/* * Copyright (C) 2021 Jeremy Cline * 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 . */ /** * MetaColorManager: * * Interfaces for managing color-related properties like * color look-up tables and color spaces. * * Each MetaBackend has a MetaColorManager which includes interfaces for querying * and altering the color-related properties for displays associated with that * backend. * * These tasks include configuring the hardware's lookup tables (LUTs) used to * apply or remove transfer functions (traditionally called "gamma"), set up * color space conversions (CSCs), and for determining or setting the output * color space and transfer function. * * Mutter itself does not store and manage device ICC profiles; this task is * handled by [colord](https://www.freedesktop.org/software/colord/). Colord * maintains a database of devices (displays, printers, etc) and color profiles, * including the default output profile for a device. Users configure colord * with their preferred color profile for a device via an external application * like GNOME Control Center or the colormgr CLI. * * Colord defines [a specification for device and profile names]( * https://github.com/hughsie/colord/blob/1.4.5/doc/device-and-profile-naming-spec.txt) * which is used to map Colord's devices to Mutter's #MetaMonitor. */ #include "config.h" #include "backends/meta-color-manager-private.h" #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" #include "meta-dbus-gsd-power-screen.h" #define DEFAULT_TEMPERATURE 6500 /* Kelvin */ enum { DEVICE_UPDATED, N_SIGNALS }; static guint signals[N_SIGNALS]; enum { PROP_0, PROP_BACKEND, N_PROPS }; static GParamSpec *obj_props[N_PROPS]; typedef struct _MetaColorManagerPrivate { MetaBackend *backend; MetaColorStore *color_store; cmsContext lcms_context; CdClient *cd_client; GCancellable *cancellable; GHashTable *devices; MetaDBusSettingsDaemonColor *gsd_color; MetaDBusSettingsDaemonPowerScreen *gsd_power_screen; gboolean is_ready; /* The temperature (in Kelvin) adjustment to apply to the color LUTs; * used to shift the screen towards red for Night Light. */ unsigned int temperature; } MetaColorManagerPrivate; G_DEFINE_TYPE_WITH_PRIVATE (MetaColorManager, meta_color_manager, G_TYPE_OBJECT) static void on_device_ready (MetaColorDevice *color_device, gboolean success, MetaColorManager *color_manager) { MetaColorManagerPrivate *priv = meta_color_manager_get_instance_private (color_manager); if (!success) { meta_topic (META_DEBUG_COLOR, "Color device '%s' failed to become ready", meta_color_device_get_id (color_device)); return; } meta_color_device_update (color_device, priv->temperature); } static void on_device_changed (MetaColorDevice *color_device, MetaColorManager *color_manager) { MetaColorManagerPrivate *priv = meta_color_manager_get_instance_private (color_manager); meta_color_device_update (color_device, priv->temperature); } static void on_device_updated (MetaColorDevice *color_device, MetaColorManager *color_manager) { g_signal_emit (color_manager, signals[DEVICE_UPDATED], 0, color_device); } 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); g_signal_connect_object (color_device, "ready", G_CALLBACK (on_device_ready), color_manager, 0); g_signal_connect_object (color_device, "changed", G_CALLBACK (on_device_changed), color_manager, 0); g_signal_connect_object (color_device, "updated", G_CALLBACK (on_device_updated), color_manager, 0); } } 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; } 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), color_manager); priv->is_ready = TRUE; } static void update_all_gamma (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; for (l = meta_monitor_manager_get_monitors (monitor_manager); l; l = l->next) { MetaMonitor *monitor = META_MONITOR (l->data); MetaColorDevice *color_device; color_device = meta_color_manager_get_color_device (color_manager, monitor); if (!color_device) continue; if (!meta_color_device_is_ready (color_device)) continue; meta_color_device_update (color_device, priv->temperature); } } static void update_temperature (MetaColorManager *color_manager) { MetaColorManagerPrivate *priv = meta_color_manager_get_instance_private (color_manager); unsigned int temperature; temperature = meta_dbus_settings_daemon_color_get_temperature (priv->gsd_color); if (temperature == 0 || priv->temperature == temperature) return; if (temperature < 1000 || temperature > 10000) { g_warning ("Invalid temperature from gsd-color: %u K", temperature); return; } priv->temperature = temperature; update_all_gamma (color_manager); } static void on_temperature_changed (MetaDBusSettingsDaemonColor *gsd_color, GParamSpec *pspec, MetaColorManager *color_manager) { update_temperature (color_manager); } static void on_gsd_color_ready (GObject *source_object, GAsyncResult *res, gpointer user_data) { MetaColorManager *color_manager = META_COLOR_MANAGER (user_data); MetaColorManagerPrivate *priv = meta_color_manager_get_instance_private (color_manager); MetaDBusSettingsDaemonColor *gsd_color; g_autoptr (GError) error = NULL; gsd_color = meta_dbus_settings_daemon_color_proxy_new_for_bus_finish (res, &error); if (!gsd_color) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; g_warning ("Failed to create gsd-color D-Bus proxy: %s", error->message); return; } meta_topic (META_DEBUG_COLOR, "Connection to org.gnome.SettingsDaemon.Color established"); priv->gsd_color = gsd_color; g_signal_connect (gsd_color, "notify::temperature", G_CALLBACK (on_temperature_changed), color_manager); update_temperature (color_manager); } static void on_gsd_power_screen_ready (GObject *source_object, GAsyncResult *res, gpointer user_data) { MetaColorManager *color_manager = META_COLOR_MANAGER (user_data); MetaColorManagerPrivate *priv = meta_color_manager_get_instance_private (color_manager); MetaDBusSettingsDaemonPowerScreen *gsd_power_screen; g_autoptr (GError) error = NULL; gsd_power_screen = meta_dbus_settings_daemon_power_screen_proxy_new_for_bus_finish (res, &error); if (!gsd_power_screen) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; g_warning ("Failed to create gsd-power-screen D-Bus proxy: %s", error->message); return; } meta_topic (META_DEBUG_COLOR, "Connection to org.gnome.SettingsDaemon.PowerScreen established"); priv->gsd_power_screen = gsd_power_screen; update_all_gamma (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->lcms_context = cmsCreateContext (NULL, NULL); priv->cancellable = g_cancellable_new (); priv->temperature = DEFAULT_TEMPERATURE; priv->cd_client = cd_client_new (); cd_client_connect (priv->cd_client, priv->cancellable, cd_client_connect_cb, color_manager); meta_dbus_settings_daemon_color_proxy_new_for_bus ( G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, "org.gnome.SettingsDaemon.Color", "/org/gnome/SettingsDaemon/Color", priv->cancellable, on_gsd_color_ready, color_manager); meta_dbus_settings_daemon_power_screen_proxy_new_for_bus ( G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, "org.gnome.SettingsDaemon.Power.Screen", "/org/gnome/SettingsDaemon/Power", priv->cancellable, on_gsd_power_screen_ready, color_manager); update_devices (color_manager); update_all_gamma (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_clear_object (&priv->gsd_power_screen); 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); } static void meta_color_manager_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { MetaColorManager *color_manager = META_COLOR_MANAGER (object); MetaColorManagerPrivate *priv = meta_color_manager_get_instance_private (color_manager); switch (prop_id) { case PROP_BACKEND: priv->backend = g_value_get_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void meta_color_manager_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MetaColorManager *color_manager = META_COLOR_MANAGER (object); MetaColorManagerPrivate *priv = meta_color_manager_get_instance_private (color_manager); switch (prop_id) { case PROP_BACKEND: g_value_set_object (value, priv->backend); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void 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; obj_props[PROP_BACKEND] = g_param_spec_object ("backend", "backend", "MetaBackend", META_TYPE_BACKEND, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, N_PROPS, obj_props); signals[DEVICE_UPDATED] = g_signal_new ("device-updated", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, META_TYPE_COLOR_DEVICE); } static void meta_color_manager_init (MetaColorManager *color_manager) { } MetaBackend * meta_color_manager_get_backend (MetaColorManager *color_manager) { MetaColorManagerPrivate *priv = meta_color_manager_get_instance_private (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; } 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) { MetaColorManagerPrivate *priv = meta_color_manager_get_instance_private (color_manager); g_autofree char *monitor_id = NULL; monitor_id = generate_monitor_id (monitor); return g_hash_table_lookup (priv->devices, monitor_id); } gboolean meta_color_manager_is_ready (MetaColorManager *color_manager) { MetaColorManagerPrivate *priv = meta_color_manager_get_instance_private (color_manager); return priv->is_ready; } int meta_color_manager_get_num_color_devices (MetaColorManager *color_manager) { MetaColorManagerPrivate *priv = meta_color_manager_get_instance_private (color_manager); return g_hash_table_size (priv->devices); } cmsContext meta_color_manager_get_lcms_context (MetaColorManager *color_manager) { MetaColorManagerPrivate *priv = meta_color_manager_get_instance_private (color_manager); return priv->lcms_context; } void meta_color_manager_set_brightness (MetaColorManager *color_manager, int brightness) { MetaColorManagerPrivate *priv = meta_color_manager_get_instance_private (color_manager); if (!priv->gsd_power_screen) { meta_topic (META_DEBUG_COLOR, "No org.gnome.SettingsDaemon.Power.Screen service available, " "not setting brightness"); return; } meta_dbus_settings_daemon_power_screen_set_brightness (priv->gsd_power_screen, brightness); }