mutter/src/backends/meta-color-device.c
Jonas Ådahl 94e1f8702d color: Handle profiles not initializing succesfully
We might fail with some part of the color profile construction and
initialization. For example there might be a system wide profile with
the same ID as one we attempt to add from a local ICC directory. When
this happens, we should drop these profiles, and use the ones from the
system instead.

Profiles may fail to initialize for other reasons too, e.g.
unpredictable colord errors, or other I/O issues.

Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/2429
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2622>
2022-09-15 11:22:54 +00:00

1231 lines
38 KiB
C

/*
* 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-device.h"
#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,
CHANGED,
UPDATED,
N_SIGNALS
};
static guint signals[N_SIGNALS];
typedef enum
{
PENDING_EDID_PROFILE = 1 << 0,
PENDING_PROFILE_READY = 1 << 1,
PENDING_CONNECTED = 1 << 2,
} PendingState;
struct _MetaColorDevice
{
GObject parent;
MetaColorManager *color_manager;
char *cd_device_id;
MetaMonitor *monitor;
CdDevice *cd_device;
MetaColorProfile *device_profile;
gulong device_profile_ready_handler_id;
MetaColorProfile *assigned_profile;
gulong assigned_profile_ready_handler_id;
GCancellable *assigned_profile_cancellable;
GCancellable *cancellable;
PendingState pending_state;
gboolean is_ready;
};
G_DEFINE_TYPE (MetaColorDevice, meta_color_device,
G_TYPE_OBJECT)
static const char *efivar_test_path = NULL;
/*
* Generate a colord DeviceId according to
* `device-and-profiling-naming-spec.txt`.
*
* A rough summary is that it should use the following format:
*
* xrandr[-{%edid_vendor_name}][-{%edid_product][-{%edid_serial}]
*
*/
static char *
generate_cd_device_id (MetaMonitor *monitor)
{
GString *device_id;
const char *vendor;
const char *product;
const char *serial;
vendor = meta_monitor_get_vendor (monitor);
product = meta_monitor_get_product (monitor);
serial = meta_monitor_get_serial (monitor);
device_id = g_string_new ("xrandr");
if (!vendor && !product && !serial)
{
g_string_append_printf (device_id,
"-%s",
meta_monitor_get_connector (monitor));
goto out;
}
if (vendor)
{
MetaBackend *backend = meta_monitor_get_backend (monitor);
g_autofree char *vendor_name = NULL;
vendor_name = meta_backend_get_vendor_name (backend, vendor);
g_string_append_printf (device_id, "-%s",
vendor_name ? vendor_name : vendor);
}
if (product)
g_string_append_printf (device_id, "-%s", product);
if (serial)
g_string_append_printf (device_id, "-%s", serial);
out:
return g_string_free (device_id, FALSE);
}
static void
ensure_default_profile_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
MetaColorStore *color_store = META_COLOR_STORE (source_object);
MetaColorDevice *color_device;
g_autoptr (MetaColorProfile) color_profile = NULL;
g_autoptr (GError) error = NULL;
color_profile = meta_color_store_ensure_colord_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 color profile from colord profile: %s",
error->message);
}
color_device = META_COLOR_DEVICE (user_data);
if (color_device->assigned_profile == color_profile)
return;
g_set_object (&color_device->assigned_profile, color_profile);
g_signal_emit (color_device, signals[CHANGED], 0);
}
static void
update_assigned_profile (MetaColorDevice *color_device)
{
MetaColorManager *color_manager = color_device->color_manager;
MetaColorStore *color_store =
meta_color_manager_get_color_store (color_manager);
CdProfile *default_profile;
GCancellable *cancellable;
default_profile = cd_device_get_default_profile (color_device->cd_device);
if (color_device->assigned_profile &&
meta_color_profile_get_cd_profile (color_device->assigned_profile) ==
default_profile)
return;
if (color_device->assigned_profile_cancellable)
{
g_cancellable_cancel (color_device->assigned_profile_cancellable);
g_clear_object (&color_device->assigned_profile_cancellable);
}
if (!default_profile)
{
g_clear_object (&color_device->assigned_profile);
return;
}
cancellable = g_cancellable_new ();
color_device->assigned_profile_cancellable = cancellable;
meta_color_store_ensure_colord_profile (color_store,
default_profile,
cancellable,
ensure_default_profile_cb,
color_device);
}
static void
on_cd_device_changed (CdDevice *cd_device,
MetaColorDevice *color_device)
{
update_assigned_profile (color_device);
}
typedef struct
{
GMainLoop *loop;
CdDevice *cd_device;
GError *error;
} FindDeviceData;
static void
on_find_device (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
CdClient *cd_client = CD_CLIENT (source_object);
FindDeviceData *data = user_data;
data->cd_device = cd_client_find_device_finish (cd_client, res, &data->error);
g_main_loop_quit (data->loop);
}
static CdDevice *
find_device_sync (CdClient *cd_client,
const char *cd_device_id,
GError **error)
{
g_autoptr (GMainContext) main_context = NULL;
g_autoptr (GMainLoop) main_loop = NULL;
FindDeviceData data = {};
main_context = g_main_context_new ();
main_loop = g_main_loop_new (main_context, FALSE);
g_main_context_push_thread_default (main_context);
data = (FindDeviceData) {
.loop = main_loop,
};
cd_client_find_device (cd_client, cd_device_id, NULL,
on_find_device,
&data);
g_main_loop_run (main_loop);
g_main_context_pop_thread_default (main_context);
if (data.error)
g_propagate_error (error, data.error);
return data.cd_device;
}
static void
meta_color_device_dispose (GObject *object)
{
MetaColorDevice *color_device = META_COLOR_DEVICE (object);
MetaColorManager *color_manager = color_device->color_manager;
CdClient *cd_client = meta_color_manager_get_cd_client (color_manager);
CdDevice *cd_device;
const char *cd_device_id;
meta_topic (META_DEBUG_COLOR,
"Removing color device '%s'", color_device->cd_device_id);
if (color_device->assigned_profile_cancellable)
{
g_cancellable_cancel (color_device->assigned_profile_cancellable);
g_clear_object (&color_device->assigned_profile_cancellable);
}
g_cancellable_cancel (color_device->cancellable);
g_clear_object (&color_device->cancellable);
g_clear_signal_handler (&color_device->device_profile_ready_handler_id,
color_device->device_profile);
g_clear_object (&color_device->assigned_profile);
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)
{
g_autoptr (GError) error = NULL;
cd_device = find_device_sync (cd_client,
cd_device_id,
&error);
if (!cd_device &&
!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
{
g_warning ("Failed to find colord device %s: %s",
cd_device_id, error->message);
}
}
if (cd_device)
cd_client_delete_device (cd_client, cd_device, NULL, NULL, NULL);
g_clear_pointer (&color_device->cd_device_id, g_free);
g_clear_object (&color_device->cd_device);
g_clear_object (&color_device->monitor);
G_OBJECT_CLASS (meta_color_device_parent_class)->dispose (object);
}
static void
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);
signals[CHANGED] =
g_signal_new ("changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, 0,
NULL, NULL, NULL,
G_TYPE_NONE, 0);
signals[UPDATED] =
g_signal_new ("updated",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, 0,
NULL, NULL, NULL,
G_TYPE_NONE, 0);
}
static void
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,
gpointer user_data)
{
CdDevice *cd_device = CD_DEVICE (source_object);
MetaColorDevice *color_device = user_data;
g_autoptr (GError) error = NULL;
if (!cd_device_connect_finish (cd_device, res, &error))
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
g_warning ("Failed to connect to colord device %s: %s",
color_device->cd_device_id,
error->message);
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;
g_signal_connect (cd_device, "changed",
G_CALLBACK (on_cd_device_changed), color_device);
update_assigned_profile (color_device);
maybe_finish_setup (color_device);
}
static void
on_profile_ready (MetaColorProfile *color_profile,
gboolean success,
MetaColorDevice *color_device)
{
if (!success)
{
g_clear_object (&color_device->device_profile);
g_cancellable_cancel (color_device->cancellable);
meta_color_device_notify_ready (color_device, FALSE);
return;
}
color_device->pending_state &= ~PENDING_PROFILE_READY;
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);
if (!meta_color_profile_is_ready (color_profile))
{
color_device->device_profile_ready_handler_id =
g_signal_connect (color_profile, "ready",
G_CALLBACK (on_profile_ready),
color_device);
color_device->pending_state |= PENDING_PROFILE_READY;
}
else
{
maybe_finish_setup (color_device);
}
}
static void
on_cd_device_created (GObject *object,
GAsyncResult *res,
gpointer user_data)
{
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;
cd_device = cd_client_create_device_finish (cd_client, res, &error);
if (!cd_device)
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
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;
}
color_device->cd_device = cd_device;
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
add_device_property (GHashTable *device_props,
const char *key,
const char *value)
{
g_hash_table_insert (device_props,
(gpointer) key,
g_strdup (value));
}
static GHashTable *
generate_color_device_props (MetaMonitor *monitor)
{
MetaBackend *backend = meta_monitor_get_backend (monitor);
GHashTable *device_props;
g_autofree char *vendor_name = NULL;
const char *edid_checksum_md5;
device_props = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
add_device_property (device_props,
CD_DEVICE_PROPERTY_KIND,
cd_device_kind_to_string (CD_DEVICE_KIND_DISPLAY));
add_device_property (device_props,
CD_DEVICE_PROPERTY_MODE,
meta_monitor_is_virtual (monitor) ?
cd_device_mode_to_string (CD_DEVICE_MODE_VIRTUAL) :
cd_device_mode_to_string (CD_DEVICE_MODE_PHYSICAL));
add_device_property (device_props,
CD_DEVICE_PROPERTY_COLORSPACE,
cd_colorspace_to_string (CD_COLORSPACE_RGB));
vendor_name =
meta_backend_get_vendor_name (backend, meta_monitor_get_vendor (monitor));
add_device_property (device_props,
CD_DEVICE_PROPERTY_VENDOR,
vendor_name);
add_device_property (device_props,
CD_DEVICE_PROPERTY_MODEL,
meta_monitor_get_product (monitor));
add_device_property (device_props,
CD_DEVICE_PROPERTY_SERIAL,
meta_monitor_get_serial (monitor));
add_device_property (device_props,
CD_DEVICE_METADATA_XRANDR_NAME,
meta_monitor_get_connector (monitor));
add_device_property (device_props,
CD_DEVICE_METADATA_OUTPUT_PRIORITY,
meta_monitor_is_primary (monitor) ?
CD_DEVICE_METADATA_OUTPUT_PRIORITY_PRIMARY :
CD_DEVICE_METADATA_OUTPUT_PRIORITY_SECONDARY);
edid_checksum_md5 = meta_monitor_get_edid_checksum_md5 (monitor);
if (edid_checksum_md5)
{
add_device_property (device_props,
CD_DEVICE_METADATA_OUTPUT_EDID_MD5,
edid_checksum_md5);
}
if (meta_monitor_is_laptop_panel (monitor))
{
add_device_property (device_props,
CD_DEVICE_PROPERTY_EMBEDDED,
NULL);
}
return device_props;
}
MetaColorDevice *
meta_color_device_new (MetaColorManager *color_manager,
MetaMonitor *monitor)
{
MetaColorDevice *color_device;
g_autoptr (GHashTable) device_props = NULL;
device_props = generate_color_device_props (monitor);
color_device = g_object_new (META_TYPE_COLOR_DEVICE, NULL);
color_device->cd_device_id = generate_cd_device_id (monitor);
color_device->monitor = g_object_ref (monitor);
color_device->cancellable = g_cancellable_new ();
color_device->color_manager = color_manager;
cd_client_create_device (meta_color_manager_get_cd_client (color_manager),
color_device->cd_device_id,
CD_OBJECT_SCOPE_TEMP,
device_props,
color_device->cancellable,
on_cd_device_created,
color_device);
return color_device;
}
void
meta_color_device_destroy (MetaColorDevice *color_device)
{
g_object_run_dispose (G_OBJECT (color_device));
g_object_unref (color_device);
}
void
meta_color_device_update_monitor (MetaColorDevice *color_device,
MetaMonitor *monitor)
{
g_warn_if_fail (meta_monitor_is_same_as (monitor, color_device->monitor));
g_set_object (&color_device->monitor, monitor);
}
const char *
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;
MetaColorCalibration *color_calibration;
} 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_clear_pointer (&data->color_calibration, meta_color_calibration_free);
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_steal_pointer (&data->color_calibration));
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,
const char *file_path,
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_PROPERTY_FILENAME, file_path);
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)
{
if (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, file_path,
&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;
}
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->color_calibration =
meta_color_calibration_new (cd_icc, NULL);
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
set_icc_checksum (CdIcc *cd_icc,
GBytes *bytes)
{
g_autofree char *md5_checksum = NULL;
md5_checksum = g_compute_checksum_for_bytes (G_CHECKSUM_MD5, bytes);
cd_icc_add_metadata (cd_icc, CD_PROFILE_METADATA_FILE_CHECKSUM, md5_checksum);
}
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) calibration_cd_icc = NULL;
g_autoptr (CdIcc) srgb_cd_icc = NULL;
meta_topic (META_DEBUG_COLOR,
"Generating ICC profile for '%s' from EFI variable",
meta_color_device_get_id (color_device));
srgb_cd_icc = cd_icc_new ();
if (!cd_icc_create_default_full (srgb_cd_icc,
CD_ICC_LOAD_FLAGS_PRIMARIES,
&error))
{
g_warning ("Failed to generate sRGB profile: %s",
error->message);
goto out;
}
calibration_cd_icc = cd_icc_new ();
if (cd_icc_load_data (calibration_cd_icc,
(uint8_t *) contents,
length,
(CD_ICC_LOAD_FLAGS_METADATA |
CD_ICC_LOAD_FLAGS_PRIMARIES),
&error))
{
GenerateProfileData *data = g_task_get_task_data (task);
const char *file_path = data->file_path;
g_autoptr (GBytes) calibration_bytes = NULL;
g_autoptr (GBytes) srgb_bytes = NULL;
CdMat3x3 csc;
srgb_bytes = cd_icc_save_data (srgb_cd_icc,
CD_ICC_SAVE_FLAGS_NONE,
&error);
if (!srgb_bytes)
{
g_warning ("Failed to save sRGB profile: %s",
error->message);
goto out;
}
calibration_bytes = g_bytes_new_take (g_steal_pointer (&contents), length);
/* Set metadata needed by colord */
cd_icc_add_metadata (calibration_cd_icc, CD_PROFILE_PROPERTY_FILENAME,
"/dev/null");
set_icc_checksum (calibration_cd_icc, calibration_bytes);
cd_icc_add_metadata (srgb_cd_icc, CD_PROFILE_PROPERTY_FILENAME,
file_path);
cd_icc_add_metadata (srgb_cd_icc, CD_PROFILE_PROPERTY_TITLE,
"Factory calibrated (sRGB)");
set_icc_checksum (srgb_cd_icc, srgb_bytes);
if (!cd_icc_utils_get_adaptation_matrix (calibration_cd_icc,
srgb_cd_icc,
&csc,
&error))
{
g_warning ("Failed to calculate adaption matrix: %s",
error->message);
goto out;
}
data->color_calibration =
meta_color_calibration_new (calibration_cd_icc, &csc);
data->cd_icc = g_steal_pointer (&srgb_cd_icc);
data->bytes = g_steal_pointer (&srgb_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);
}
out:
create_device_profile_from_edid (color_device, g_steal_pointer (&task));
}
void
meta_set_color_efivar_test_path (const char *path)
{
efivar_test_path = path;
}
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)) ||
efivar_test_path)
{
g_autoptr (GFile) file = NULL;
if (efivar_test_path)
file = g_file_new_for_path (efivar_test_path);
else
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;
}
MetaColorProfile *
meta_color_device_get_assigned_profile (MetaColorDevice *color_device)
{
return color_device->assigned_profile;
}
void
meta_color_device_update (MetaColorDevice *color_device,
unsigned int temperature)
{
MetaColorProfile *color_profile;
MetaMonitor *monitor;
size_t lut_size;
color_profile = meta_color_device_get_assigned_profile (color_device);
if (!color_profile)
return;
monitor = color_device->monitor;
if (!meta_monitor_is_active (monitor))
return;
meta_topic (META_DEBUG_COLOR,
"Updating device '%s' (%s) using color profile '%s' "
"and temperature %uK",
meta_color_device_get_id (color_device),
meta_monitor_get_connector (monitor),
meta_color_profile_get_id (color_profile),
temperature);
if (meta_monitor_is_laptop_panel (monitor))
{
const char *brightness_profile;
brightness_profile =
meta_color_profile_get_brightness_profile (color_profile);
if (brightness_profile)
{
meta_topic (META_DEBUG_COLOR,
"Setting brightness to %s%% from brightness profile",
brightness_profile);
meta_color_manager_set_brightness (color_device->color_manager,
atoi (brightness_profile));
}
}
lut_size = meta_monitor_get_gamma_lut_size (monitor);
if (lut_size > 0)
{
g_autoptr (MetaGammaLut) lut = NULL;
lut = meta_color_profile_generate_gamma_lut (color_profile,
temperature,
lut_size);
meta_monitor_set_gamma_lut (monitor, lut);
}
g_signal_emit (color_device, signals[UPDATED], 0);
}