diff --git a/src/backends/meta-backend-types.h b/src/backends/meta-backend-types.h index 0a24734db..3e8b46881 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 681212a75..57f606414 100644 --- a/src/backends/meta-color-device.c +++ b/src/backends/meta-color-device.c @@ -24,9 +24,30 @@ #include +#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 8cda7d103..6022fc0a8 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 +#include #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 b358a359a..3e5e52360 100644 --- a/src/backends/meta-color-manager-private.h +++ b/src/backends/meta-color-manager-private.h @@ -21,6 +21,7 @@ #include #include +#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 cae3d923c..0004af63c 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 000000000..48b39fa09 --- /dev/null +++ b/src/backends/meta-color-profile.c @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2007 William Jon McCann + * Copyright (C) 2011-2013 Richard Hughes + * 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 . + */ + +#include "config.h" + +#include "backends/meta-color-profile.h" + +#include +#include + +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 000000000..7dae53083 --- /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 . + */ + +#ifndef META_COLOR_PROFILE_H +#define META_COLOR_PROFILE_H + +#include +#include +#include + +#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 000000000..860d81a89 --- /dev/null +++ b/src/backends/meta-color-store.c @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2007 William Jon McCann + * Copyright (C) 2011-2013 Richard Hughes + * 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 . + */ + +#include "config.h" + +#include "backends/meta-color-store.h" + +#include +#include + +#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 000000000..74bac9688 --- /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 . + */ + +#ifndef META_COLOR_STORE_H +#define META_COLOR_STORE_H + +#include +#include + +#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 0b5d6c4fc..af492ed69 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 15072d1bc..7fefd3314 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