[mutter] color: Generate and store ICC profiles from EDID or EFI



commit 062abe01b3ad1a2d9529937eb1c6c9d3a42d6c18
Author: Jonas Ã…dahl <jadahl gmail com>
Date:   Mon Nov 29 20:44:56 2021 +0100

    color: Generate and store ICC profiles from EDID or EFI
    
    Just as gsd-color does, generate color profiles. This can either be done
    from EFI, if available and the color device is associated with a built
    in panel, or from the EDID. If no source for a profile is found, none is
    created.
    
    The ICC profiles are also stored on disk so that they can be read by
    e.g. colord. The on disk stored profiles will only be used for storing,
    not reading the profiles, as the autogenerated ones will no matter what
    always be loaded to verify the on disk profiles are up to date. If a on
    disk profile is not, it will be replaced. This is so that fixes or
    improvements to the profile generation will be made available despite
    having run an older version earlier.
    
    After generating, add some metadata about the generated file itself
    needed by colord, i.e. file MD5 checksum and the file path.
    
    Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2164>

 src/backends/meta-backend-types.h         |   2 +
 src/backends/meta-color-device.c          | 593 +++++++++++++++++++++++++++++-
 src/backends/meta-color-device.h          |  17 +
 src/backends/meta-color-manager-private.h |   3 +
 src/backends/meta-color-manager.c         |  15 +
 src/backends/meta-color-profile.c         | 103 ++++++
 src/backends/meta-color-profile.h         |  47 +++
 src/backends/meta-color-store.c           | 209 +++++++++++
 src/backends/meta-color-store.h           |  43 +++
 src/meson.build                           |   4 +
 src/tests/color-management-tests.c        |  66 ++++
 11 files changed, 1101 insertions(+), 1 deletion(-)
---
diff --git a/src/backends/meta-backend-types.h b/src/backends/meta-backend-types.h
index 0a24734db4..3e8b468814 100644
--- a/src/backends/meta-backend-types.h
+++ b/src/backends/meta-backend-types.h
@@ -25,6 +25,8 @@ typedef struct _MetaBackend MetaBackend;
 
 typedef struct _MetaColorDevice MetaColorDevice;
 typedef struct _MetaColorManager MetaColorManager;
+typedef struct _MetaColorProfile MetaColorProfile;
+typedef struct _MetaColorStore MetaColorStore;
 
 typedef struct _MetaMonitorManager MetaMonitorManager;
 
diff --git a/src/backends/meta-color-device.c b/src/backends/meta-color-device.c
index 681212a758..57f6064141 100644
--- a/src/backends/meta-color-device.c
+++ b/src/backends/meta-color-device.c
@@ -24,9 +24,30 @@
 
 #include <colord.h>
 
+#include "backends/meta-color-device.h"
 #include "backends/meta-color-manager-private.h"
+#include "backends/meta-color-profile.h"
+#include "backends/meta-color-store.h"
 #include "backends/meta-monitor.h"
 
+#define EFI_PANEL_COLOR_INFO_PATH \
+  "/sys/firmware/efi/efivars/INTERNAL_PANEL_COLOR_INFO-01e1ada1-79f2-46b3-8d3e-71fc0996ca6b"
+
+enum
+{
+  READY,
+
+  N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+
+typedef enum
+{
+  PENDING_EDID_PROFILE = 1 << 0,
+  PENDING_CONNECTED = 1 << 1,
+} PendingState;
+
 struct _MetaColorDevice
 {
   GObject parent;
@@ -37,7 +58,12 @@ struct _MetaColorDevice
   MetaMonitor *monitor;
   CdDevice *cd_device;
 
+  MetaColorProfile *device_profile;
+
   GCancellable *cancellable;
+
+  PendingState pending_state;
+  gboolean is_ready;
 };
 
 G_DEFINE_TYPE (MetaColorDevice, meta_color_device,
@@ -155,6 +181,8 @@ meta_color_device_dispose (GObject *object)
   g_cancellable_cancel (color_device->cancellable);
   g_clear_object (&color_device->cancellable);
 
+  g_clear_object (&color_device->device_profile);
+
   cd_device = color_device->cd_device;
   cd_device_id = color_device->cd_device_id;
   if (!cd_device && cd_device_id)
@@ -188,6 +216,14 @@ meta_color_device_class_init (MetaColorDeviceClass *klass)
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
   object_class->dispose = meta_color_device_dispose;
+
+  signals[READY] =
+    g_signal_new ("ready",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST, 0,
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1,
+                  G_TYPE_BOOLEAN);
 }
 
 static void
@@ -195,6 +231,26 @@ meta_color_device_init (MetaColorDevice *color_device)
 {
 }
 
+static void
+meta_color_device_notify_ready (MetaColorDevice *color_device,
+                                gboolean         success)
+{
+  color_device->is_ready = success;
+  g_signal_emit (color_device, signals[READY], 0, success);
+}
+
+static void
+maybe_finish_setup (MetaColorDevice *color_device)
+{
+  if (color_device->pending_state)
+    return;
+
+  meta_topic (META_DEBUG_COLOR, "Color device '%s' is ready",
+              color_device->cd_device_id);
+
+  meta_color_device_notify_ready (color_device, TRUE);
+}
+
 static void
 on_cd_device_connected (GObject      *source_object,
                         GAsyncResult *res,
@@ -212,8 +268,53 @@ on_cd_device_connected (GObject      *source_object,
       g_warning ("Failed to connect to colord device %s: %s",
                  color_device->cd_device_id,
                  error->message);
-      return;
+
+      g_cancellable_cancel (color_device->cancellable);
+      meta_color_device_notify_ready (color_device, FALSE);
+    }
+  else
+    {
+      meta_topic (META_DEBUG_COLOR, "Color device '%s' connected",
+                  color_device->cd_device_id);
+    }
+
+  color_device->pending_state &= ~PENDING_CONNECTED;
+
+  maybe_finish_setup (color_device);
+}
+
+static void
+ensure_device_profile_cb (GObject      *source_object,
+                          GAsyncResult *res,
+                          gpointer      user_data)
+{
+  MetaColorStore *color_store = META_COLOR_STORE (source_object);
+  MetaColorDevice *color_device = META_COLOR_DEVICE (user_data);
+  MetaColorProfile *color_profile;
+  g_autoptr (GError) error = NULL;
+
+  color_profile = meta_color_store_ensure_device_profile_finish (color_store,
+                                                                 res,
+                                                                 &error);
+
+  if (!color_profile)
+    {
+      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        return;
+
+      g_warning ("Failed to create device color profile: %s", error->message);
+
+      g_cancellable_cancel (color_device->cancellable);
+      meta_color_device_notify_ready (color_device, FALSE);
     }
+
+  meta_topic (META_DEBUG_COLOR, "Color device '%s' generated",
+              color_device->cd_device_id);
+
+  color_device->pending_state &= ~PENDING_EDID_PROFILE;
+  g_set_object (&color_device->device_profile, color_profile);
+
+  maybe_finish_setup (color_device);
 }
 
 static void
@@ -223,6 +324,8 @@ on_cd_device_created (GObject      *object,
 {
   CdClient *cd_client = CD_CLIENT (object);
   MetaColorDevice *color_device = user_data;
+  MetaColorManager *color_manager;
+  MetaColorStore *color_store;
   CdDevice *cd_device;
   g_autoptr (GError) error = NULL;
 
@@ -235,6 +338,7 @@ on_cd_device_created (GObject      *object,
       g_warning ("Failed to create colord device for '%s': %s",
                  color_device->cd_device_id,
                  error->message);
+      meta_color_device_notify_ready (color_device, FALSE);
       return;
     }
 
@@ -242,6 +346,16 @@ on_cd_device_created (GObject      *object,
 
   cd_device_connect (cd_device, color_device->cancellable,
                      on_cd_device_connected, color_device);
+  color_device->pending_state |= PENDING_CONNECTED;
+
+  color_manager = color_device->color_manager;
+  color_store = meta_color_manager_get_color_store (color_manager);
+  if (meta_color_store_ensure_device_profile (color_store,
+                                              color_device,
+                                              color_device->cancellable,
+                                              ensure_device_profile_cb,
+                                              color_device))
+    color_device->pending_state |= PENDING_EDID_PROFILE;
 }
 
 static void
@@ -361,8 +475,485 @@ meta_color_device_get_id (MetaColorDevice *color_device)
   return color_device->cd_device_id;
 }
 
+typedef struct
+{
+  MetaColorDevice *color_device;
+
+  char *file_path;
+  GBytes *bytes;
+  CdIcc *cd_icc;
+} GenerateProfileData;
+
+static void
+generate_profile_data_free (GenerateProfileData *data)
+{
+  g_free (data->file_path);
+  g_clear_object (&data->cd_icc);
+  g_clear_pointer (&data->bytes, g_bytes_unref);
+  g_free (data);
+}
+
+static void
+on_profile_written (GObject      *source_object,
+                    GAsyncResult *res,
+                    gpointer      user_data)
+{
+  GFile *file = G_FILE (source_object);
+  g_autoptr (GTask) task = G_TASK (user_data);
+  GenerateProfileData *data = g_task_get_task_data (task);
+  MetaColorManager *color_manager = data->color_device->color_manager;
+  g_autoptr (GError) error = NULL;
+  MetaColorProfile *color_profile;
+
+  if (!g_file_replace_contents_finish (file, res, NULL, &error))
+    {
+      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        {
+          g_task_return_error (task, g_steal_pointer (&error));
+          return;
+        }
+
+      g_prefix_error (&error, "Failed to write ICC profile to %s:",
+                      g_file_peek_path (file));
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  meta_topic (META_DEBUG_COLOR, "On-disk device profile '%s' updated",
+              g_file_peek_path (file));
+
+  color_profile =
+    meta_color_profile_new_from_icc (color_manager,
+                                     g_steal_pointer (&data->cd_icc),
+                                     g_steal_pointer (&data->bytes));
+  g_task_return_pointer (task, color_profile, g_object_unref);
+}
+
+static void
+do_save_icc_profile (GTask *task)
+{
+  GenerateProfileData *data = g_task_get_task_data (task);
+  const uint8_t *profile_data;
+  size_t profile_data_size;
+  g_autoptr (GFile) file = NULL;
+
+  profile_data = g_bytes_get_data (data->bytes, &profile_data_size);
+
+  file = g_file_new_for_path (data->file_path);
+  g_file_replace_contents_async  (file,
+                                  (const char *) profile_data,
+                                  profile_data_size,
+                                  NULL,
+                                  FALSE,
+                                  G_FILE_CREATE_NONE,
+                                  g_task_get_cancellable (task),
+                                  on_profile_written,
+                                  task);
+}
+
+static void
+on_directories_created (GObject      *source_object,
+                        GAsyncResult *res,
+                        gpointer      user_data)
+{
+  GFile *directory = G_FILE (source_object);
+  GTask *thread_task = G_TASK (res);
+  GTask *task = G_TASK (user_data);
+
+  if (g_cancellable_is_cancelled (g_task_get_cancellable (thread_task)))
+    {
+      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+                               "Cancelled");
+      return;
+    }
+
+  meta_topic (META_DEBUG_COLOR, "ICC profile directory '%s' created",
+              g_file_peek_path (directory));
+
+  do_save_icc_profile (task);
+}
+
+static void
+create_directories_in_thread (GTask        *thread_task,
+                              gpointer      source_object,
+                              gpointer      task_data,
+                              GCancellable *cancellable)
+{
+  GFile *directory = G_FILE (source_object);
+  g_autoptr (GError) error = NULL;
+
+  if (!g_file_make_directory_with_parents (directory, cancellable, &error))
+    g_task_return_error (thread_task, g_steal_pointer (&error));
+  else
+    g_task_return_boolean (thread_task, TRUE);
+}
+
+static void
+create_icc_profiles_directory (GFile *directory,
+                               GTask *task)
+{
+  g_autoptr (GTask) thread_task = NULL;
+
+  thread_task = g_task_new (G_OBJECT (directory),
+                            g_task_get_cancellable (task),
+                            on_directories_created, task);
+  g_task_run_in_thread (thread_task, create_directories_in_thread);
+}
+
+static void
+on_directory_queried (GObject      *source_object,
+                      GAsyncResult *res,
+                      gpointer      user_data)
+{
+  GFile *directory = G_FILE (source_object);
+  g_autoptr (GTask) task = G_TASK (user_data);
+  g_autoptr (GFileInfo) file_info = NULL;
+  g_autoptr (GError) error = NULL;
+
+  file_info = g_file_query_info_finish (directory, res, &error);
+  if (!file_info)
+    {
+      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        {
+          g_task_return_error (task, g_steal_pointer (&error));
+          return;
+        }
+      else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+        {
+          create_icc_profiles_directory (directory, g_steal_pointer (&task));
+          return;
+        }
+      else
+        {
+          g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
+                                   "Failed to ensure data directory: %s",
+                                   error->message);
+          return;
+        }
+    }
+
+  do_save_icc_profile (g_steal_pointer (&task));
+}
+
+static void
+save_icc_profile (const char *file_path,
+                  GTask      *task)
+{
+  g_autoptr (GFile) file = NULL;
+  g_autoptr (GFile) directory = NULL;
+
+  file = g_file_new_for_path (file_path);
+  directory = g_file_get_parent (file);
+  g_file_query_info_async (directory,
+                           G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                           G_FILE_QUERY_INFO_NONE,
+                           G_PRIORITY_DEFAULT,
+                           g_task_get_cancellable (task),
+                           on_directory_queried,
+                           task);
+}
+
+static CdIcc *
+create_icc_profile_from_edid (MetaColorDevice     *color_device,
+                              const MetaEdidInfo  *edid_info,
+                              GError             **error)
+{
+  MetaColorManager *color_manager = color_device->color_manager;
+  MetaMonitor *monitor = color_device->monitor;
+  g_autoptr (CdIcc) cd_icc = NULL;
+  cmsCIExyYTRIPLE chroma;
+  cmsCIExyY white_point;
+  cmsToneCurve *transfer_curve[3] = { NULL, NULL, NULL };
+  cmsContext lcms_context;
+  const char *product;
+  const char *vendor;
+  const char *serial;
+  g_autofree char *vendor_name = NULL;
+  cmsHPROFILE lcms_profile;
+
+  cd_icc = cd_icc_new ();
+
+  chroma.Red.x = edid_info->red_x;
+  chroma.Red.y = edid_info->red_y;
+  chroma.Green.x = edid_info->green_x;
+  chroma.Green.y = edid_info->green_y;
+  chroma.Blue.x = edid_info->blue_x;
+  chroma.Blue.y = edid_info->blue_y;
+  white_point.x = edid_info->white_x;
+  white_point.y = edid_info->white_y;
+  white_point.Y = 1.0;
+
+  /* Estimate the transfer function for the gamma */
+  transfer_curve[0] = cmsBuildGamma (NULL, edid_info->gamma);
+  transfer_curve[1] = transfer_curve[0];
+  transfer_curve[2] = transfer_curve[0];
+
+  lcms_context = meta_color_manager_get_lcms_context (color_manager);
+  lcms_profile = cmsCreateRGBProfileTHR (lcms_context,
+                                         &white_point,
+                                         &chroma,
+                                         transfer_curve);
+  cmsSetHeaderRenderingIntent (lcms_profile, INTENT_PERCEPTUAL);
+  cmsSetDeviceClass (lcms_profile, cmsSigDisplayClass);
+
+  cmsFreeToneCurve (transfer_curve[0]);
+
+  if (!cd_icc_load_handle (cd_icc, lcms_profile,
+                           CD_ICC_LOAD_FLAGS_PRIMARIES, error))
+    {
+      cmsCloseProfile (lcms_profile);
+      return NULL;
+    }
+
+  cd_icc_add_metadata (cd_icc,
+                       CD_PROFILE_METADATA_DATA_SOURCE,
+                       CD_PROFILE_METADATA_DATA_SOURCE_EDID);
+  cd_icc_set_copyright (cd_icc, NULL,
+                        "This profile is free of known copyright restrictions.");
+
+  product = meta_monitor_get_product (monitor);
+  vendor = meta_monitor_get_vendor (monitor);
+  serial = meta_monitor_get_serial (monitor);
+  if (vendor)
+    {
+      MetaBackend *backend = meta_monitor_get_backend (monitor);
+
+      vendor_name = meta_backend_get_vendor_name (backend, vendor);
+    }
+
+  /* set 'ICC meta Tag for Monitor Profiles' data */
+  cd_icc_add_metadata (cd_icc, CD_PROFILE_METADATA_EDID_MD5,
+                       meta_monitor_get_edid_checksum_md5 (monitor));
+  if (product)
+    cd_icc_add_metadata (cd_icc, CD_PROFILE_METADATA_EDID_MODEL, product);
+  if (serial)
+    cd_icc_add_metadata (cd_icc, CD_PROFILE_METADATA_EDID_SERIAL, serial);
+  if (vendor)
+    cd_icc_add_metadata (cd_icc, CD_PROFILE_METADATA_EDID_MNFT, vendor);
+  if (vendor_name)
+    {
+      cd_icc_add_metadata (cd_icc, CD_PROFILE_METADATA_EDID_VENDOR,
+                           vendor_name);
+    }
+
+  /* Set high level monitor details metadata */
+  if (!product)
+    product = "Unknown monitor";
+  cd_icc_set_model (cd_icc, NULL, product);
+  cd_icc_set_description (cd_icc, NULL,
+                          meta_monitor_get_display_name (monitor));
+
+  if (!vendor_name && vendor)
+    vendor_name = g_strdup (vendor);
+  else
+    vendor_name = g_strdup ("Unknown vendor");
+  cd_icc_set_manufacturer (cd_icc, NULL, vendor_name);
+
+  /* Set the framework creator metadata */
+  cd_icc_add_metadata (cd_icc,
+                       CD_PROFILE_METADATA_CMF_PRODUCT,
+                       PACKAGE_NAME);
+  cd_icc_add_metadata (cd_icc,
+                       CD_PROFILE_METADATA_CMF_BINARY,
+                       PACKAGE_NAME);
+  cd_icc_add_metadata (cd_icc,
+                       CD_PROFILE_METADATA_CMF_VERSION,
+                       PACKAGE_VERSION);
+  cd_icc_add_metadata (cd_icc,
+                       CD_PROFILE_METADATA_MAPPING_DEVICE_ID,
+                       color_device->cd_device_id);
+
+  return g_steal_pointer (&cd_icc);
+}
+
+static void
+create_device_profile_from_edid (MetaColorDevice *color_device,
+                                 GTask           *task)
+{
+  const MetaEdidInfo *edid_info;
+
+  edid_info = meta_monitor_get_edid_info (color_device->monitor);
+  if (edid_info)
+    {
+      g_autoptr (CdIcc) cd_icc = NULL;
+      GBytes *bytes;
+      g_autoptr (GError) error = NULL;
+      GenerateProfileData *data = g_task_get_task_data (task);
+      const char *file_path = data->file_path;
+      g_autofree char *file_md5_checksum = NULL;
+
+      meta_topic (META_DEBUG_COLOR,
+                  "Generating ICC profile for '%s' from EDID",
+                  meta_color_device_get_id (color_device));
+
+      cd_icc = create_icc_profile_from_edid (color_device, edid_info, &error);
+      if (!cd_icc)
+        {
+          g_task_return_error (task, g_steal_pointer (&error));
+          g_object_unref (task);
+          return;
+        }
+
+      bytes = cd_icc_save_data (cd_icc, CD_ICC_SAVE_FLAGS_NONE, &error);
+      if (!bytes)
+        {
+          g_task_return_error (task, g_steal_pointer (&error));
+          g_object_unref (task);
+          return;
+        }
+
+      /* 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);
+
+      data->cd_icc = g_steal_pointer (&cd_icc);
+      data->bytes = bytes;
+      save_icc_profile (file_path, task);
+    }
+  else
+    {
+      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
+                               "No EDID available");
+      g_object_unref (task);
+    }
+}
+
+static void
+on_efi_panel_color_info_loaded (GObject      *source_object,
+                                GAsyncResult *res,
+                                gpointer      user_data)
+{
+  GFile *file = G_FILE (source_object);
+  g_autoptr (GTask) task = G_TASK (user_data);
+  MetaColorDevice *color_device =
+    META_COLOR_DEVICE (g_task_get_source_object (task));
+  g_autoptr (GError) error = NULL;
+  g_autofree char *contents = NULL;
+  size_t length;
+
+  if (g_file_load_contents_finish (file, res,
+                                   &contents,
+                                   &length,
+                                   NULL,
+                                   &error))
+    {
+      g_autoptr (CdIcc) cd_icc = NULL;
+
+      meta_topic (META_DEBUG_COLOR,
+                  "Generating ICC profile for '%s' from EFI variable",
+                  meta_color_device_get_id (color_device));
+
+      cd_icc = cd_icc_new ();
+      if (cd_icc_load_data (cd_icc,
+                            (uint8_t *) contents,
+                            length,
+                            CD_ICC_LOAD_FLAGS_METADATA,
+                            &error))
+        {
+          GenerateProfileData *data = g_task_get_task_data (task);
+          const char *file_path = data->file_path;
+          g_autofree char *file_md5_checksum = NULL;
+          GBytes *bytes;
+
+          bytes = g_bytes_new_take (g_steal_pointer (&contents), length);
+
+          /* 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);
+
+          data->cd_icc = g_steal_pointer (&cd_icc);
+          data->bytes = bytes;
+          save_icc_profile (file_path, g_steal_pointer (&task));
+          return;
+        }
+      else
+        {
+          g_warning ("Failed to parse EFI panel color ICC profile: %s",
+                     error->message);
+        }
+    }
+  else
+    {
+      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        {
+          g_task_return_error (task, g_steal_pointer (&error));
+          return;
+        }
+
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+        g_warning ("Failed to read EFI panel color info: %s", error->message);
+    }
+
+  create_device_profile_from_edid (color_device, g_steal_pointer (&task));
+}
+
+void
+meta_color_device_generate_profile (MetaColorDevice     *color_device,
+                                    const char          *file_path,
+                                    GCancellable        *cancellable,
+                                    GAsyncReadyCallback  callback,
+                                    gpointer             user_data)
+{
+  GTask *task;
+  GenerateProfileData *data;
+
+  task = g_task_new (G_OBJECT (color_device), cancellable, callback, user_data);
+  g_task_set_source_tag (task, meta_color_device_generate_profile);
+
+  data = g_new0 (GenerateProfileData, 1);
+  data->color_device = color_device;
+  data->file_path = g_strdup (file_path);
+  g_task_set_task_data (task, data,
+                        (GDestroyNotify) generate_profile_data_free);
+
+  if (meta_monitor_is_laptop_panel (color_device->monitor) &&
+      meta_monitor_supports_color_transform (color_device->monitor))
+    {
+      g_autoptr (GFile) file = NULL;
+
+      file = g_file_new_for_path (EFI_PANEL_COLOR_INFO_PATH);
+      g_file_load_contents_async (file,
+                                  cancellable,
+                                  on_efi_panel_color_info_loaded,
+                                  task);
+    }
+  else
+    {
+      create_device_profile_from_edid (color_device, task);
+    }
+}
+
+MetaColorProfile *
+meta_color_device_generate_profile_finish (MetaColorDevice  *color_device,
+                                           GAsyncResult     *res,
+                                           GError          **error)
+{
+  g_assert (g_task_get_source_tag (G_TASK (res)) ==
+            meta_color_device_generate_profile);
+  return g_task_propagate_pointer (G_TASK (res), error);
+}
+
 MetaMonitor *
 meta_color_device_get_monitor (MetaColorDevice *color_device)
 {
   return color_device->monitor;
 }
+
+MetaColorProfile *
+meta_color_device_get_device_profile (MetaColorDevice *color_device)
+{
+  return color_device->device_profile;
+}
+
+gboolean
+meta_color_device_is_ready (MetaColorDevice *color_device)
+{
+  return color_device->is_ready;
+}
diff --git a/src/backends/meta-color-device.h b/src/backends/meta-color-device.h
index 8cda7d103a..6022fc0a8d 100644
--- a/src/backends/meta-color-device.h
+++ b/src/backends/meta-color-device.h
@@ -19,6 +19,7 @@
 #define META_COLOR_DEVICE_H
 
 #include <glib-object.h>
+#include <gio/gio.h>
 
 #include "backends/meta-backend-types.h"
 #include "core/util-private.h"
@@ -41,4 +42,20 @@ const char * meta_color_device_get_id (MetaColorDevice *color_device);
 META_EXPORT_TEST
 MetaMonitor * meta_color_device_get_monitor (MetaColorDevice *color_device);
 
+META_EXPORT_TEST
+MetaColorProfile * meta_color_device_get_device_profile (MetaColorDevice *color_device);
+
+void meta_color_device_generate_profile (MetaColorDevice     *color_device,
+                                         const char          *file_path,
+                                         GCancellable        *cancellable,
+                                         GAsyncReadyCallback  callback,
+                                         gpointer             user_data);
+
+MetaColorProfile * meta_color_device_generate_profile_finish (MetaColorDevice  *color_device,
+                                                              GAsyncResult     *res,
+                                                              GError          **error);
+
+META_EXPORT_TEST
+gboolean meta_color_device_is_ready (MetaColorDevice *color_device);
+
 #endif /* META_COLOR_DEVICE_H */
diff --git a/src/backends/meta-color-manager-private.h b/src/backends/meta-color-manager-private.h
index b358a359ad..3e5e52360d 100644
--- a/src/backends/meta-color-manager-private.h
+++ b/src/backends/meta-color-manager-private.h
@@ -21,6 +21,7 @@
 #include <colord.h>
 #include <lcms2.h>
 
+#include "backends/meta-backend-types.h"
 #include "backends/meta-color-manager.h"
 
 struct _MetaColorManagerClass
@@ -30,6 +31,8 @@ struct _MetaColorManagerClass
 
 CdClient * meta_color_manager_get_cd_client (MetaColorManager *color_manager);
 
+MetaColorStore * meta_color_manager_get_color_store (MetaColorManager *color_manager);
+
 META_EXPORT_TEST
 gboolean meta_color_manager_is_ready (MetaColorManager *color_manager);
 
diff --git a/src/backends/meta-color-manager.c b/src/backends/meta-color-manager.c
index cae3d923c9..0004af63ce 100644
--- a/src/backends/meta-color-manager.c
+++ b/src/backends/meta-color-manager.c
@@ -49,6 +49,7 @@
 
 #include "backends/meta-backend-types.h"
 #include "backends/meta-color-device.h"
+#include "backends/meta-color-store.h"
 #include "backends/meta-monitor.h"
 
 #include "meta-dbus-gsd-color.h"
@@ -68,6 +69,8 @@ typedef struct _MetaColorManagerPrivate
 {
   MetaBackend *backend;
 
+  MetaColorStore *color_store;
+
   cmsContext lcms_context;
 
   CdClient *cd_client;
@@ -203,6 +206,8 @@ cd_client_connect_cb (GObject      *source_object,
       return;
     }
 
+  priv->color_store = meta_color_store_new (color_manager);
+
   update_devices (color_manager);
   g_signal_connect (monitor_manager, "monitors-changed-internal",
                     G_CALLBACK (on_monitors_changed),
@@ -274,6 +279,7 @@ meta_color_manager_finalize (GObject *object)
   g_clear_object (&priv->cancellable);
   g_clear_pointer (&priv->devices, g_hash_table_unref);
   g_clear_object (&priv->gsd_color);
+  g_clear_object (&priv->color_store);
   g_clear_pointer (&priv->lcms_context, cmsDeleteContext);
 
   G_OBJECT_CLASS (meta_color_manager_parent_class)->finalize (object);
@@ -365,6 +371,15 @@ meta_color_manager_get_cd_client (MetaColorManager *color_manager)
   return priv->cd_client;
 }
 
+MetaColorStore *
+meta_color_manager_get_color_store (MetaColorManager *color_manager)
+{
+  MetaColorManagerPrivate *priv =
+    meta_color_manager_get_instance_private (color_manager);
+
+  return priv->color_store;
+}
+
 MetaColorDevice *
 meta_color_manager_get_color_device (MetaColorManager *color_manager,
                                      MetaMonitor      *monitor)
diff --git a/src/backends/meta-color-profile.c b/src/backends/meta-color-profile.c
new file mode 100644
index 0000000000..48b39fa09e
--- /dev/null
+++ b/src/backends/meta-color-profile.c
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2007 William Jon McCann <mccann jhu edu>
+ * Copyright (C) 2011-2013 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2020 NVIDIA CORPORATION
+ * Copyright (C) 2021 Red Hat Inc.
+ *
+ * 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/>.
+ */
+
+#include "config.h"
+
+#include "backends/meta-color-profile.h"
+
+#include <colord.h>
+#include <gio/gio.h>
+
+struct _MetaColorProfile
+{
+  GObject parent;
+
+  MetaColorManager *color_manager;
+
+  CdIcc *cd_icc;
+  GBytes *bytes;
+};
+
+G_DEFINE_TYPE (MetaColorProfile, meta_color_profile,
+               G_TYPE_OBJECT)
+
+static void
+meta_color_profile_finalize (GObject *object)
+{
+  MetaColorProfile *color_profile = META_COLOR_PROFILE (object);
+
+  g_clear_object (&color_profile->cd_icc);
+  g_clear_pointer (&color_profile->bytes, g_bytes_unref);
+
+  G_OBJECT_CLASS (meta_color_profile_parent_class)->finalize (object);
+}
+
+static void
+meta_color_profile_class_init (MetaColorProfileClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = meta_color_profile_finalize;
+}
+
+static void
+meta_color_profile_init (MetaColorProfile *color_profile)
+{
+}
+
+MetaColorProfile *
+meta_color_profile_new_from_icc (MetaColorManager *color_manager,
+                                 CdIcc            *cd_icc,
+                                 GBytes           *raw_bytes)
+{
+  MetaColorProfile *color_profile;
+
+  color_profile = g_object_new (META_TYPE_COLOR_PROFILE, NULL);
+  color_profile->color_manager = color_manager;
+  color_profile->cd_icc = cd_icc;
+  color_profile->bytes = raw_bytes;
+
+  return color_profile;
+}
+
+gboolean
+meta_color_profile_equals_bytes (MetaColorProfile *color_profile,
+                                 GBytes           *bytes)
+{
+  return g_bytes_equal (color_profile->bytes, bytes);
+}
+
+const uint8_t *
+meta_color_profile_get_data (MetaColorProfile *color_profile)
+{
+  return g_bytes_get_data (color_profile->bytes, NULL);
+}
+
+size_t
+meta_color_profile_get_data_size (MetaColorProfile *color_profile)
+{
+  return g_bytes_get_size (color_profile->bytes);
+}
+
+CdIcc *
+meta_color_profile_get_cd_icc (MetaColorProfile *color_profile)
+{
+  return color_profile->cd_icc;
+}
diff --git a/src/backends/meta-color-profile.h b/src/backends/meta-color-profile.h
new file mode 100644
index 0000000000..7dae530830
--- /dev/null
+++ b/src/backends/meta-color-profile.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 Red Hat Inc.
+ *
+ * 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/>.
+ */
+
+#ifndef META_COLOR_PROFILE_H
+#define META_COLOR_PROFILE_H
+
+#include <colord.h>
+#include <glib-object.h>
+#include <stdint.h>
+
+#include "backends/meta-backend-types.h"
+#include "core/util-private.h"
+
+#define META_TYPE_COLOR_PROFILE (meta_color_profile_get_type ())
+G_DECLARE_FINAL_TYPE (MetaColorProfile, meta_color_profile,
+                      META, COLOR_PROFILE,
+                      GObject)
+
+MetaColorProfile * meta_color_profile_new_from_icc (MetaColorManager *color_manager,
+                                                    CdIcc            *icc,
+                                                    GBytes           *raw_bytes);
+
+gboolean meta_color_profile_equals_bytes (MetaColorProfile *color_profile,
+                                          GBytes           *bytes);
+
+const uint8_t * meta_color_profile_get_data (MetaColorProfile *color_profile);
+
+size_t meta_color_profile_get_data_size (MetaColorProfile *color_profile);
+
+META_EXPORT_TEST
+CdIcc * meta_color_profile_get_cd_icc (MetaColorProfile *color_profile);
+
+#endif /* META_COLOR_PROFILE_H */
diff --git a/src/backends/meta-color-store.c b/src/backends/meta-color-store.c
new file mode 100644
index 0000000000..860d81a898
--- /dev/null
+++ b/src/backends/meta-color-store.c
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2007 William Jon McCann <mccann jhu edu>
+ * Copyright (C) 2011-2013 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2020 NVIDIA CORPORATION
+ * Copyright (C) 2021 Red Hat Inc.
+ *
+ * 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/>.
+ */
+
+#include "config.h"
+
+#include "backends/meta-color-store.h"
+
+#include <colord.h>
+#include <stdint.h>
+
+#include "backends/meta-color-device.h"
+#include "backends/meta-color-profile.h"
+#include "backends/meta-monitor.h"
+
+struct _MetaColorStore
+{
+  GObject parent;
+
+  MetaColorManager *color_manager;
+
+  GHashTable *profiles;
+  GHashTable *device_profiles;
+  GHashTable *pending_device_profiles;
+};
+
+typedef struct
+{
+  MetaColorStore *color_store;
+  char *key;
+} EnsureDeviceProfileData;
+
+G_DEFINE_TYPE (MetaColorStore, meta_color_store,
+               G_TYPE_OBJECT)
+
+static void
+meta_color_store_finalize (GObject *object)
+{
+  MetaColorStore *color_store = META_COLOR_STORE (object);
+
+  g_clear_pointer (&color_store->device_profiles, g_hash_table_unref);
+  g_clear_pointer (&color_store->pending_device_profiles, g_hash_table_unref);
+
+  G_OBJECT_CLASS (meta_color_store_parent_class)->finalize (object);
+}
+
+static void
+meta_color_store_class_init (MetaColorStoreClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = meta_color_store_finalize;
+}
+
+static void
+meta_color_store_init (MetaColorStore *color_store)
+{
+}
+
+MetaColorStore *
+meta_color_store_new (MetaColorManager *color_manager)
+{
+  MetaColorStore *color_store;
+
+  color_store = g_object_new (META_TYPE_COLOR_STORE, NULL);
+  color_store->color_manager = color_manager;
+  color_store->device_profiles =
+    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);
+
+  return color_store;
+}
+
+static void
+ensure_device_profile_data_free (EnsureDeviceProfileData *data)
+{
+  g_free (data->key);
+  g_free (data);
+}
+
+static void
+on_profile_generated (GObject      *source_object,
+                      GAsyncResult *res,
+                      gpointer      user_data)
+{
+  MetaColorDevice *color_device = META_COLOR_DEVICE (source_object);
+  g_autoptr (GTask) task = G_TASK (user_data);
+  g_autoptr (GError) error = NULL;
+  MetaColorProfile *color_profile;
+
+  color_profile = meta_color_device_generate_profile_finish (color_device,
+                                                             res,
+                                                             &error);
+  if (!color_profile)
+    {
+      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        {
+          g_task_return_error (task, g_steal_pointer (&error));
+          return;
+        }
+
+      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
+                               "Failed to generate and read ICC profile: %s",
+                               error->message);
+      return;
+    }
+
+  g_task_return_pointer (task, color_profile, g_object_unref);
+}
+
+gboolean
+meta_color_store_ensure_device_profile (MetaColorStore      *color_store,
+                                        MetaColorDevice     *color_device,
+                                        GCancellable        *cancellable,
+                                        GAsyncReadyCallback  callback,
+                                        gpointer             user_data)
+{
+  MetaMonitor *monitor;
+  const char *edid_checksum_md5;
+  g_autoptr (GTask) task = NULL;
+  g_autofree char *file_name = NULL;
+  char *file_path;
+  EnsureDeviceProfileData *data;
+  MetaColorProfile *color_profile;
+
+  monitor = meta_color_device_get_monitor (color_device);
+  edid_checksum_md5 = meta_monitor_get_edid_checksum_md5 (monitor);
+  if (!edid_checksum_md5)
+    return FALSE;
+
+  task = g_task_new (G_OBJECT (color_store), cancellable, callback, user_data);
+  g_task_set_source_tag (task, meta_color_store_ensure_device_profile);
+
+  file_name = g_strdup_printf ("edid-%s.icc", edid_checksum_md5);
+  file_path = g_build_filename (g_get_user_data_dir (),
+                                "icc", file_name, NULL);
+
+  data = g_new0 (EnsureDeviceProfileData, 1);
+  data->color_store = color_store;
+  data->key = g_strdup (meta_color_device_get_id (color_device));
+  g_task_set_task_data (task, data,
+                        (GDestroyNotify) ensure_device_profile_data_free);
+
+  color_profile = g_hash_table_lookup (color_store->device_profiles, data->key);
+  if (color_profile)
+    {
+      g_task_return_pointer (task,
+                             g_object_ref (color_profile),
+                             g_object_unref);
+      return TRUE;
+    }
+
+  if (g_hash_table_contains (color_store->pending_device_profiles, data->key))
+    {
+      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
+                               "Profile generation already in progress");
+      return TRUE;
+    }
+
+  g_hash_table_add (color_store->pending_device_profiles, g_strdup (data->key));
+
+  meta_color_device_generate_profile (color_device,
+                                      file_path,
+                                      cancellable,
+                                      on_profile_generated,
+                                      g_steal_pointer (&task));
+  return TRUE;
+}
+
+MetaColorProfile *
+meta_color_store_ensure_device_profile_finish (MetaColorStore  *color_store,
+                                               GAsyncResult    *res,
+                                               GError         **error)
+{
+  GTask *task = G_TASK (res);
+  EnsureDeviceProfileData *data = g_task_get_task_data (task);
+  g_autoptr (MetaColorProfile) color_profile = NULL;
+
+  g_assert (g_task_get_source_tag (task) ==
+            meta_color_store_ensure_device_profile);
+
+  g_hash_table_remove (color_store->pending_device_profiles, data->key);
+
+  color_profile = g_task_propagate_pointer (task, error);
+  if (!color_profile)
+    return NULL;
+
+  g_hash_table_insert (color_store->device_profiles,
+                       g_steal_pointer (&data->key),
+                       g_object_ref (color_profile));
+  return color_profile;
+}
diff --git a/src/backends/meta-color-store.h b/src/backends/meta-color-store.h
new file mode 100644
index 0000000000..74bac9688a
--- /dev/null
+++ b/src/backends/meta-color-store.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 Red Hat Inc.
+ *
+ * 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/>.
+ */
+
+#ifndef META_COLOR_STORE_H
+#define META_COLOR_STORE_H
+
+#include <gio/gio.h>
+#include <glib-object.h>
+
+#include "backends/meta-backend-types.h"
+
+#define META_TYPE_COLOR_STORE (meta_color_store_get_type ())
+G_DECLARE_FINAL_TYPE (MetaColorStore, meta_color_store,
+                      META, COLOR_STORE,
+                      GObject)
+
+MetaColorStore * meta_color_store_new (MetaColorManager *color_manager);
+
+gboolean meta_color_store_ensure_device_profile (MetaColorStore      *color_store,
+                                                 MetaColorDevice     *color_device,
+                                                 GCancellable        *cancellable,
+                                                 GAsyncReadyCallback  callback,
+                                                 gpointer             user_data);
+
+MetaColorProfile * meta_color_store_ensure_device_profile_finish (MetaColorStore  *color_store,
+                                                                  GAsyncResult    *res,
+                                                                  GError         **error);
+
+#endif /* META_COLOR_STORE_H */
diff --git a/src/meson.build b/src/meson.build
index 0b5d6c4fc0..af492ed693 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -188,6 +188,10 @@ mutter_sources = [
   'backends/meta-color-manager.c',
   'backends/meta-color-manager.h',
   'backends/meta-color-manager-private.h',
+  'backends/meta-color-profile.c',
+  'backends/meta-color-profile.h',
+  'backends/meta-color-store.c',
+  'backends/meta-color-store.h',
   'backends/meta-crtc-mode.c',
   'backends/meta-crtc-mode.h',
   'backends/meta-crtc.c',
diff --git a/src/tests/color-management-tests.c b/src/tests/color-management-tests.c
index 15072d1bc3..7fefd3314a 100644
--- a/src/tests/color-management-tests.c
+++ b/src/tests/color-management-tests.c
@@ -22,11 +22,14 @@
 
 #include "backends/meta-color-device.h"
 #include "backends/meta-color-manager-private.h"
+#include "backends/meta-color-profile.h"
 #include "meta-test/meta-context-test.h"
 #include "tests/meta-monitor-test-utils.h"
 
 static MetaContext *test_context;
 
+#define PRIMARY_EPSILON 0.000015
+
 static MonitorTestCaseSetup base_monitor_setup = {
   .modes = {
     {
@@ -205,6 +208,67 @@ meta_test_color_management_device_basic (void)
     }
 }
 
+static void
+meta_test_color_management_profile_device (void)
+{
+  MetaBackend *backend = meta_context_get_backend (test_context);
+  MetaMonitorManager *monitor_manager =
+    meta_backend_get_monitor_manager (backend);
+  MetaMonitorManagerTest *monitor_manager_test =
+    META_MONITOR_MANAGER_TEST (monitor_manager);
+  MetaColorManager *color_manager =
+    meta_backend_get_color_manager (backend);
+  MetaEdidInfo edid_info;
+  MonitorTestCaseSetup test_case_setup = base_monitor_setup;
+  MetaMonitorTestSetup *test_setup;
+  MetaMonitor *monitor;
+  MetaColorDevice *color_device;
+  MetaColorProfile *color_profile;
+  CdIcc *cd_icc;
+  const CdColorXYZ *red;
+  const CdColorXYZ *green;
+  const CdColorXYZ *blue;
+  const CdColorXYZ *white;
+
+  edid_info = CALTECH_MONITOR_EDID;
+  test_case_setup.outputs[0].edid_info = edid_info;
+  test_case_setup.outputs[0].has_edid_info = TRUE;
+  test_setup = meta_create_monitor_test_setup (backend, &test_case_setup,
+                                               MONITOR_TEST_FLAG_NO_STORED);
+  meta_monitor_manager_test_emulate_hotplug (monitor_manager_test, test_setup);
+
+  monitor = meta_monitor_manager_get_monitors (monitor_manager)->data;
+  color_device = meta_color_manager_get_color_device (color_manager, monitor);
+  g_assert_nonnull (color_device);
+
+  while (!meta_color_device_is_ready (color_device))
+    g_main_context_iteration (NULL, TRUE);
+
+  color_profile = meta_color_device_get_device_profile (color_device);
+  g_assert_nonnull (color_profile);
+  cd_icc = meta_color_profile_get_cd_icc (color_profile);
+  g_assert_nonnull (cd_icc);
+
+  red = cd_icc_get_red (cd_icc);
+  green = cd_icc_get_green (cd_icc);
+  blue = cd_icc_get_blue (cd_icc);
+  white = cd_icc_get_white (cd_icc);
+
+  /* Make sure we generate the same values as gsd-color did. */
+  g_assert_cmpfloat_with_epsilon (red->X, 0.549637, PRIMARY_EPSILON);
+  g_assert_cmpfloat_with_epsilon (red->Y, 0.250671, PRIMARY_EPSILON);
+  g_assert_cmpfloat_with_epsilon (red->Z, 0.000977, PRIMARY_EPSILON);
+  g_assert_cmpfloat_with_epsilon (green->X, 0.277420, PRIMARY_EPSILON);
+  g_assert_cmpfloat_with_epsilon (green->Y, 0.689514, PRIMARY_EPSILON);
+  g_assert_cmpfloat_with_epsilon (green->Z, 0.052185, PRIMARY_EPSILON);
+  g_assert_cmpfloat_with_epsilon (blue->X, 0.137146 , PRIMARY_EPSILON);
+  g_assert_cmpfloat_with_epsilon (blue->Y, 0.059814, PRIMARY_EPSILON);
+  g_assert_cmpfloat_with_epsilon (blue->Z, 0.771744, PRIMARY_EPSILON);
+  g_assert_cmpfloat_with_epsilon (white->X, 0.961090088, PRIMARY_EPSILON);
+  g_assert_cmpfloat_with_epsilon (white->Y, 1.0, PRIMARY_EPSILON);
+  g_assert_cmpfloat_with_epsilon (white->Z, 1.10479736, PRIMARY_EPSILON);
+}
+
 static MetaMonitorTestSetup *
 create_stage_view_test_setup (MetaBackend *backend)
 {
@@ -230,6 +294,8 @@ init_tests (void)
 
   g_test_add_func ("/color-management/device/basic",
                    meta_test_color_management_device_basic);
+  g_test_add_func ("/color-management/profile/device",
+                   meta_test_color_management_profile_device);
 }
 
 int


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