mirror of
https://github.com/brl/mutter.git
synced 2024-12-02 12:50:53 -05:00
94e1f8702d
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>
1231 lines
38 KiB
C
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);
|
|
}
|