diff --git a/meson.build b/meson.build index cce2d74db..7af8a6aa7 100644 --- a/meson.build +++ b/meson.build @@ -13,6 +13,7 @@ mutter_srcdir = meson.current_source_dir() mutter_builddir = meson.current_build_dir() # generic version requirements +colord_req = '>= 1.4.5' fribidi_req = '>= 1.0.0' glib_req = '>= 2.69.0' gi_req = '>= 0.9.5' @@ -124,6 +125,7 @@ ice_dep = dependency('ice') atk_dep = dependency('atk', version: atk_req) libcanberra_dep = dependency('libcanberra', version: libcanberra_req) dbus_dep = dependency('dbus-1') +colord_dep = dependency('colord', version: colord_req) have_wayland = get_option('wayland') # For now always require X11 support diff --git a/src/backends/meta-color-device.c b/src/backends/meta-color-device.c new file mode 100644 index 000000000..ab7fadb4d --- /dev/null +++ b/src/backends/meta-color-device.c @@ -0,0 +1,362 @@ +/* + * 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-device.h" + +#include + +#include "backends/meta-color-manager-private.h" +#include "backends/meta-monitor.h" + +struct _MetaColorDevice +{ + GObject parent; + + MetaColorManager *color_manager; + + char *cd_device_id; + MetaMonitor *monitor; + CdDevice *cd_device; + + GCancellable *cancellable; +}; + +G_DEFINE_TYPE (MetaColorDevice, meta_color_device, + G_TYPE_OBJECT) + +/* + * 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); +} + +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); + + g_cancellable_cancel (color_device->cancellable); + g_clear_object (&color_device->cancellable); + + 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; +} + +static void +meta_color_device_init (MetaColorDevice *color_device) +{ +} + +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); + return; + } +} + +static void +on_cd_device_created (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + CdClient *cd_client = CD_CLIENT (object); + MetaColorDevice *color_device = user_data; + 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); + return; + } + + color_device->cd_device = cd_device; + + cd_device_connect (cd_device, color_device->cancellable, + on_cd_device_connected, color_device); +} + +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; +} diff --git a/src/backends/meta-color-device.h b/src/backends/meta-color-device.h new file mode 100644 index 000000000..e01391ce0 --- /dev/null +++ b/src/backends/meta-color-device.h @@ -0,0 +1,40 @@ +/* + * 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_DEVICE_H +#define META_COLOR_DEVICE_H + +#include + +#include "backends/meta-backend-types.h" + +#define META_TYPE_COLOR_DEVICE (meta_color_device_get_type ()) +G_DECLARE_FINAL_TYPE (MetaColorDevice, meta_color_device, + META, COLOR_DEVICE, + GObject) + +MetaColorDevice * meta_color_device_new (MetaColorManager *color_manager, + MetaMonitor *monitor); + +void meta_color_device_destroy (MetaColorDevice *color_device); + +void meta_color_device_update_monitor (MetaColorDevice *color_device, + MetaMonitor *monitor); + +const char * meta_color_device_get_id (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 2e656ae41..db5d425e9 100644 --- a/src/backends/meta-color-manager-private.h +++ b/src/backends/meta-color-manager-private.h @@ -18,6 +18,8 @@ #ifndef META_COLOR_MANAGER_PRIVATE_H #define META_COLOR_MANAGER_PRIVATE_H +#include + #include "backends/meta-color-manager.h" struct _MetaColorManagerClass @@ -25,4 +27,6 @@ struct _MetaColorManagerClass GObjectClass parent_class; }; +CdClient * meta_color_manager_get_cd_client (MetaColorManager *color_manager); + #endif /* META_COLOR_MANAGER_PRIVATE_H */ diff --git a/src/backends/meta-color-manager.c b/src/backends/meta-color-manager.c index 3e9fdcfca..c7bdc77e4 100644 --- a/src/backends/meta-color-manager.c +++ b/src/backends/meta-color-manager.c @@ -48,6 +48,7 @@ #include "backends/meta-color-manager-private.h" #include "backends/meta-backend-types.h" +#include "backends/meta-color-device.h" #include "backends/meta-monitor.h" enum @@ -64,10 +65,170 @@ static GParamSpec *obj_props[N_PROPS]; typedef struct _MetaColorManagerPrivate { MetaBackend *backend; + + CdClient *cd_client; + GCancellable *cancellable; + + GHashTable *devices; } MetaColorManagerPrivate; G_DEFINE_TYPE_WITH_PRIVATE (MetaColorManager, meta_color_manager, G_TYPE_OBJECT) +static char * +generate_monitor_id (MetaMonitor *monitor) +{ + const char *vendor; + const char *product; + const char *serial; + GString *id; + + vendor = meta_monitor_get_vendor (monitor); + product = meta_monitor_get_product (monitor); + serial = meta_monitor_get_serial (monitor); + if (!vendor && !product && !serial) + return g_strdup (meta_monitor_get_connector (monitor)); + + id = g_string_new (""); + + if (vendor) + g_string_append_printf (id, "v:%s", vendor); + if (product) + g_string_append_printf (id, "%sp:%s", id->len > 0 ? ";" : "", product); + if (serial) + g_string_append_printf (id, "%sp:%s", id->len > 0 ? ";" : "", serial); + + return g_string_free (id, FALSE); +} + +static void +update_devices (MetaColorManager *color_manager) +{ + MetaColorManagerPrivate *priv = + meta_color_manager_get_instance_private (color_manager); + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (priv->backend); + GList *l; + GHashTable *devices; + + devices = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) meta_color_device_destroy); + for (l = meta_monitor_manager_get_monitors (monitor_manager); l; l = l->next) + { + MetaMonitor *monitor = META_MONITOR (l->data); + g_autofree char *monitor_id = NULL; + g_autofree char *stolen_monitor_id = NULL; + MetaColorDevice *color_device; + + monitor_id = generate_monitor_id (monitor); + + if (priv->devices && + g_hash_table_steal_extended (priv->devices, + monitor_id, + (gpointer *) &stolen_monitor_id, + (gpointer *) &color_device)) + { + meta_topic (META_DEBUG_COLOR, + "Updating color device '%s' monitor instance", + meta_color_device_get_id (color_device)); + meta_color_device_update_monitor (color_device, monitor); + g_hash_table_insert (devices, + g_steal_pointer (&monitor_id), + color_device); + } + else + { + color_device = meta_color_device_new (color_manager, monitor); + meta_topic (META_DEBUG_COLOR, + "Created new color device '%s' for monitor %s", + meta_color_device_get_id (color_device), + meta_monitor_get_connector (monitor)); + g_hash_table_insert (devices, + g_steal_pointer (&monitor_id), + color_device); + } + } + + if (priv->devices) + { + if (g_hash_table_size (priv->devices) > 0) + { + meta_topic (META_DEBUG_COLOR, "Removing %u color devices", + g_hash_table_size (priv->devices)); + } + g_clear_pointer (&priv->devices, g_hash_table_unref); + } + priv->devices = devices; +} + +static void +on_monitors_changed (MetaMonitorManager *monitor_manager, + MetaColorManager *color_manager) +{ + update_devices (color_manager); +} + +static void +cd_client_connect_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + CdClient *client = CD_CLIENT (source_object); + MetaColorManager *color_manager = META_COLOR_MANAGER (user_data); + MetaColorManagerPrivate *priv = + meta_color_manager_get_instance_private (color_manager); + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (priv->backend); + g_autoptr (GError) error = NULL; + + if (!cd_client_connect_finish (client, res, &error)) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to connect to colord daemon: %s", error->message); + return; + } + + if (!cd_client_get_has_server (client)) + { + g_warning ("There is no colord server available"); + return; + } + + update_devices (color_manager); + g_signal_connect (monitor_manager, "monitors-changed-internal", + G_CALLBACK (on_monitors_changed), + color_manager); +} + +static void +meta_color_manager_constructed (GObject *object) +{ + MetaColorManager *color_manager = META_COLOR_MANAGER (object); + MetaColorManagerPrivate *priv = + meta_color_manager_get_instance_private (color_manager); + + priv->cancellable = g_cancellable_new (); + + priv->cd_client = cd_client_new (); + cd_client_connect (priv->cd_client, priv->cancellable, cd_client_connect_cb, + color_manager); +} + +static void +meta_color_manager_finalize (GObject *object) +{ + MetaColorManager *color_manager = META_COLOR_MANAGER (object); + MetaColorManagerPrivate *priv = + meta_color_manager_get_instance_private (color_manager); + + g_cancellable_cancel (priv->cancellable); + g_clear_object (&priv->cancellable); + g_clear_pointer (&priv->devices, g_hash_table_unref); + + G_OBJECT_CLASS (meta_color_manager_parent_class)->finalize (object); +} + static void meta_color_manager_set_property (GObject *object, guint prop_id, @@ -115,6 +276,8 @@ meta_color_manager_class_init (MetaColorManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->constructed = meta_color_manager_constructed; + object_class->finalize = meta_color_manager_finalize; object_class->set_property = meta_color_manager_set_property; object_class->get_property = meta_color_manager_get_property; @@ -142,3 +305,12 @@ meta_color_manager_get_backend (MetaColorManager *color_manager) return priv->backend; } + +CdClient * +meta_color_manager_get_cd_client (MetaColorManager *color_manager) +{ + MetaColorManagerPrivate *priv = + meta_color_manager_get_instance_private (color_manager); + + return priv->cd_client; +} diff --git a/src/meson.build b/src/meson.build index 6a88f5d5c..160606dc5 100644 --- a/src/meson.build +++ b/src/meson.build @@ -22,6 +22,7 @@ mutter_pkg_deps = [ ] mutter_pkg_private_deps = [ + colord_dep, gmodule_no_export_dep, gnome_settings_daemon_dep, json_glib_dep, @@ -181,6 +182,8 @@ mutter_sources = [ 'backends/meta-backend-private.h', 'backends/meta-barrier.c', 'backends/meta-barrier-private.h', + 'backends/meta-color-device.c', + 'backends/meta-color-device.h', 'backends/meta-color-manager.c', 'backends/meta-color-manager.h', 'backends/meta-color-manager-private.h',