summaryrefslogtreecommitdiffstats
path: root/plugins/color/gsd-color-state.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/color/gsd-color-state.c')
-rw-r--r--plugins/color/gsd-color-state.c1592
1 files changed, 1592 insertions, 0 deletions
diff --git a/plugins/color/gsd-color-state.c b/plugins/color/gsd-color-state.c
new file mode 100644
index 0000000..746cf23
--- /dev/null
+++ b/plugins/color/gsd-color-state.c
@@ -0,0 +1,1592 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ * Copyright (C) 2011-2013 Richard Hughes <richard@hughsie.com>
+ *
+ * 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 <glib/gi18n.h>
+#include <colord.h>
+#include <gdk/gdk.h>
+#include <stdlib.h>
+#include <lcms2.h>
+#include <canberra-gtk.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-rr.h>
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+#include "gnome-settings-bus.h"
+
+#include "gsd-color-manager.h"
+#include "gsd-color-state.h"
+#include "gcm-edid.h"
+
+#define GSD_DBUS_NAME "org.gnome.SettingsDaemon"
+#define GSD_DBUS_PATH "/org/gnome/SettingsDaemon"
+#define GSD_DBUS_BASE_INTERFACE "org.gnome.SettingsDaemon"
+
+static void gcm_session_set_gamma_for_all_devices (GsdColorState *state);
+
+struct _GsdColorState
+{
+ GObject parent;
+
+ GCancellable *cancellable;
+ GsdSessionManager *session;
+ CdClient *client;
+ GnomeRRScreen *state_screen;
+ GHashTable *edid_cache;
+ GdkWindow *gdk_window;
+ gboolean session_is_active;
+ GHashTable *device_assign_hash;
+ guint color_temperature;
+};
+
+static void gsd_color_state_class_init (GsdColorStateClass *klass);
+static void gsd_color_state_init (GsdColorState *color_state);
+static void gsd_color_state_finalize (GObject *object);
+
+G_DEFINE_TYPE (GsdColorState, gsd_color_state, G_TYPE_OBJECT)
+
+/* see http://www.oyranos.org/wiki/index.php?title=ICC_Profiles_in_X_Specification_0.3 */
+#define GCM_ICC_PROFILE_IN_X_VERSION_MAJOR 0
+#define GCM_ICC_PROFILE_IN_X_VERSION_MINOR 3
+
+typedef struct {
+ guint32 red;
+ guint32 green;
+ guint32 blue;
+} GnomeRROutputClutItem;
+
+GQuark
+gsd_color_state_error_quark (void)
+{
+ static GQuark quark = 0;
+ if (!quark)
+ quark = g_quark_from_static_string ("gsd_color_state_error");
+ return quark;
+}
+
+static GcmEdid *
+gcm_session_get_output_edid (GsdColorState *state, GnomeRROutput *output, GError **error)
+{
+ const guint8 *data;
+ gsize size;
+ GcmEdid *edid = NULL;
+ gboolean ret;
+
+ /* can we find it in the cache */
+ edid = g_hash_table_lookup (state->edid_cache,
+ gnome_rr_output_get_name (output));
+ if (edid != NULL) {
+ g_object_ref (edid);
+ return edid;
+ }
+
+ /* parse edid */
+ data = gnome_rr_output_get_edid_data (output, &size);
+ if (data == NULL || size == 0) {
+ g_set_error_literal (error,
+ GNOME_RR_ERROR,
+ GNOME_RR_ERROR_UNKNOWN,
+ "unable to get EDID for output");
+ return NULL;
+ }
+ edid = gcm_edid_new ();
+ ret = gcm_edid_parse (edid, data, size, error);
+ if (!ret) {
+ g_object_unref (edid);
+ return NULL;
+ }
+
+ /* add to cache */
+ g_hash_table_insert (state->edid_cache,
+ g_strdup (gnome_rr_output_get_name (output)),
+ g_object_ref (edid));
+
+ return edid;
+}
+
+static gboolean
+gcm_session_screen_set_icc_profile (GsdColorState *state,
+ const gchar *filename,
+ GError **error)
+{
+ gchar *data = NULL;
+ gsize length;
+ guint version_data;
+
+ g_return_val_if_fail (filename != NULL, FALSE);
+
+ /* wayland */
+ if (state->gdk_window == NULL) {
+ g_debug ("not setting atom as running under wayland");
+ return TRUE;
+ }
+
+ g_debug ("setting root window ICC profile atom from %s", filename);
+
+ /* get contents of file */
+ if (!g_file_get_contents (filename, &data, &length, error))
+ return FALSE;
+
+ /* set profile property */
+ gdk_property_change (state->gdk_window,
+ gdk_atom_intern_static_string ("_ICC_PROFILE"),
+ gdk_atom_intern_static_string ("CARDINAL"),
+ 8,
+ GDK_PROP_MODE_REPLACE,
+ (const guchar *) data, length);
+
+ /* set version property */
+ version_data = GCM_ICC_PROFILE_IN_X_VERSION_MAJOR * 100 +
+ GCM_ICC_PROFILE_IN_X_VERSION_MINOR * 1;
+ gdk_property_change (state->gdk_window,
+ gdk_atom_intern_static_string ("_ICC_PROFILE_IN_X_VERSION"),
+ gdk_atom_intern_static_string ("CARDINAL"),
+ 8,
+ GDK_PROP_MODE_REPLACE,
+ (const guchar *) &version_data, 1);
+
+ g_free (data);
+ return TRUE;
+}
+
+void
+gsd_color_state_set_temperature (GsdColorState *state, guint temperature)
+{
+ g_return_if_fail (GSD_IS_COLOR_STATE (state));
+
+ if (state->color_temperature == temperature)
+ return;
+
+ state->color_temperature = temperature;
+ gcm_session_set_gamma_for_all_devices (state);
+}
+
+guint
+gsd_color_state_get_temperature (GsdColorState *state)
+{
+ g_return_val_if_fail (GSD_IS_COLOR_STATE (state), 0);
+ return state->color_temperature;
+}
+
+static gchar *
+gcm_session_get_output_id (GsdColorState *state, GnomeRROutput *output)
+{
+ const gchar *name;
+ const gchar *serial;
+ const gchar *vendor;
+ GcmEdid *edid = NULL;
+ GString *device_id;
+ GError *error = NULL;
+
+ /* all output devices are prefixed with this */
+ device_id = g_string_new ("xrandr");
+
+ /* get the output EDID if possible */
+ edid = gcm_session_get_output_edid (state, output, &error);
+ if (edid == NULL) {
+ g_debug ("no edid for %s [%s], falling back to connection name",
+ gnome_rr_output_get_name (output),
+ error->message);
+ g_error_free (error);
+ g_string_append_printf (device_id,
+ "-%s",
+ gnome_rr_output_get_name (output));
+ goto out;
+ }
+
+ /* check EDID data is okay to use */
+ vendor = gcm_edid_get_vendor_name (edid);
+ name = gcm_edid_get_monitor_name (edid);
+ serial = gcm_edid_get_serial_number (edid);
+ if (vendor == NULL && name == NULL && serial == NULL) {
+ g_debug ("edid invalid for %s, falling back to connection name",
+ gnome_rr_output_get_name (output));
+ g_string_append_printf (device_id,
+ "-%s",
+ gnome_rr_output_get_name (output));
+ goto out;
+ }
+
+ /* use EDID data */
+ if (vendor != NULL)
+ g_string_append_printf (device_id, "-%s", vendor);
+ if (name != NULL)
+ g_string_append_printf (device_id, "-%s", name);
+ if (serial != NULL)
+ g_string_append_printf (device_id, "-%s", serial);
+out:
+ if (edid != NULL)
+ g_object_unref (edid);
+ return g_string_free (device_id, FALSE);
+}
+
+typedef struct {
+ GsdColorState *state;
+ CdProfile *profile;
+ CdDevice *device;
+ guint32 output_id;
+} GcmSessionAsyncHelper;
+
+static void
+gcm_session_async_helper_free (GcmSessionAsyncHelper *helper)
+{
+ if (helper->state != NULL)
+ g_object_unref (helper->state);
+ if (helper->profile != NULL)
+ g_object_unref (helper->profile);
+ if (helper->device != NULL)
+ g_object_unref (helper->device);
+ g_free (helper);
+}
+
+static gboolean
+gcm_utils_mkdir_for_filename (GFile *file, GError **error)
+{
+ gboolean ret = FALSE;
+ GFile *parent_dir = NULL;
+
+ /* get parent directory */
+ parent_dir = g_file_get_parent (file);
+ if (parent_dir == NULL) {
+ g_set_error_literal (error,
+ GSD_COLOR_MANAGER_ERROR,
+ GSD_COLOR_MANAGER_ERROR_FAILED,
+ "could not get parent dir");
+ goto out;
+ }
+
+ /* ensure desination does not already exist */
+ ret = g_file_query_exists (parent_dir, NULL);
+ if (ret)
+ goto out;
+ ret = g_file_make_directory_with_parents (parent_dir, NULL, error);
+ if (!ret)
+ goto out;
+out:
+ if (parent_dir != NULL)
+ g_object_unref (parent_dir);
+ return ret;
+}
+
+static gboolean
+gcm_get_system_icc_profile (GsdColorState *state,
+ GFile *file)
+{
+ const char efi_path[] = "/sys/firmware/efi/efivars/INTERNAL_PANEL_COLOR_INFO-01e1ada1-79f2-46b3-8d3e-71fc0996ca6b";
+ /* efi variables have a 4-byte header */
+ const int efi_var_header_length = 4;
+ g_autoptr(GFile) efi_file = g_file_new_for_path (efi_path);
+ gboolean ret;
+ g_autofree char *data = NULL;
+ gsize length;
+ g_autoptr(GError) error = NULL;
+
+ ret = g_file_query_exists (efi_file, NULL);
+ if (!ret)
+ return FALSE;
+
+ ret = g_file_load_contents (efi_file,
+ NULL /* cancellable */,
+ &data,
+ &length,
+ NULL /* etag_out */,
+ &error);
+
+ if (!ret) {
+ g_warning ("failed to read EFI system color profile: %s",
+ error->message);
+ return FALSE;
+ }
+
+ if (length <= efi_var_header_length) {
+ g_warning ("EFI system color profile was too short");
+ return FALSE;
+ }
+
+ ret = g_file_replace_contents (file,
+ data + efi_var_header_length,
+ length - efi_var_header_length,
+ NULL /* etag */,
+ FALSE /* make_backup */,
+ G_FILE_CREATE_NONE,
+ NULL /* new_etag */,
+ NULL /* cancellable */,
+ &error);
+ if (!ret) {
+ g_warning ("failed to write system color profile: %s",
+ error->message);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gcm_apply_create_icc_profile_for_edid (GsdColorState *state,
+ CdDevice *device,
+ GcmEdid *edid,
+ GFile *file,
+ GError **error)
+{
+ CdIcc *icc = NULL;
+ const gchar *data;
+ gboolean ret = FALSE;
+
+ /* ensure the per-user directory exists */
+ ret = gcm_utils_mkdir_for_filename (file, error);
+ if (!ret)
+ goto out;
+
+ /* create our generated profile */
+ icc = cd_icc_new ();
+ ret = cd_icc_create_from_edid (icc,
+ gcm_edid_get_gamma (edid),
+ gcm_edid_get_red (edid),
+ gcm_edid_get_green (edid),
+ gcm_edid_get_blue (edid),
+ gcm_edid_get_white (edid),
+ error);
+ if (!ret)
+ goto out;
+
+ /* set copyright */
+ cd_icc_set_copyright (icc, NULL,
+ /* deliberately not translated */
+ "This profile is free of known copyright restrictions.");
+
+ /* set model and title */
+ data = gcm_edid_get_monitor_name (edid);
+ if (data == NULL)
+ data = cd_client_get_system_model (state->client);
+ if (data == NULL)
+ data = "Unknown monitor";
+ cd_icc_set_model (icc, NULL, data);
+ cd_icc_set_description (icc, NULL, data);
+
+ /* get manufacturer */
+ data = gcm_edid_get_vendor_name (edid);
+ if (data == NULL)
+ data = cd_client_get_system_vendor (state->client);
+ if (data == NULL)
+ data = "Unknown vendor";
+ cd_icc_set_manufacturer (icc, NULL, data);
+
+ /* set the framework creator metadata */
+ cd_icc_add_metadata (icc,
+ CD_PROFILE_METADATA_CMF_PRODUCT,
+ PACKAGE_NAME);
+ cd_icc_add_metadata (icc,
+ CD_PROFILE_METADATA_CMF_BINARY,
+ PACKAGE_NAME);
+ cd_icc_add_metadata (icc,
+ CD_PROFILE_METADATA_CMF_VERSION,
+ PACKAGE_VERSION);
+ cd_icc_add_metadata (icc,
+ CD_PROFILE_METADATA_MAPPING_DEVICE_ID,
+ cd_device_get_id (device));
+
+ /* set 'ICC meta Tag for Monitor Profiles' data */
+ cd_icc_add_metadata (icc, CD_PROFILE_METADATA_EDID_MD5, gcm_edid_get_checksum (edid));
+ data = gcm_edid_get_monitor_name (edid);
+ if (data != NULL)
+ cd_icc_add_metadata (icc, CD_PROFILE_METADATA_EDID_MODEL, data);
+ data = gcm_edid_get_serial_number (edid);
+ if (data != NULL)
+ cd_icc_add_metadata (icc, CD_PROFILE_METADATA_EDID_SERIAL, data);
+ data = gcm_edid_get_pnp_id (edid);
+ if (data != NULL)
+ cd_icc_add_metadata (icc, CD_PROFILE_METADATA_EDID_MNFT, data);
+ data = gcm_edid_get_vendor_name (edid);
+ if (data != NULL)
+ cd_icc_add_metadata (icc, CD_PROFILE_METADATA_EDID_VENDOR, data);
+
+ /* save */
+ ret = cd_icc_save_file (icc, file, CD_ICC_SAVE_FLAGS_NONE, NULL, error);
+ if (!ret)
+ goto out;
+out:
+ if (icc != NULL)
+ g_object_unref (icc);
+ return ret;
+}
+
+static GPtrArray *
+gcm_session_generate_vcgt (CdProfile *profile, guint color_temperature, guint size)
+{
+ GnomeRROutputClutItem *tmp;
+ GPtrArray *array = NULL;
+ const cmsToneCurve **vcgt;
+ cmsFloat32Number in;
+ guint i;
+ cmsHPROFILE lcms_profile;
+ CdIcc *icc = NULL;
+ CdColorRGB temp;
+
+ /* invalid size */
+ if (size == 0)
+ goto out;
+
+ /* open file */
+ icc = cd_profile_load_icc (profile, CD_ICC_LOAD_FLAGS_NONE, NULL, NULL);
+ if (icc == NULL)
+ goto out;
+
+ /* get tone curves from profile */
+ lcms_profile = cd_icc_get_handle (icc);
+ vcgt = cmsReadTag (lcms_profile, cmsSigVcgtTag);
+ if (vcgt == NULL || vcgt[0] == NULL) {
+ g_debug ("profile does not have any VCGT data");
+ goto out;
+ }
+
+ /* get the color temperature */
+ if (!cd_color_get_blackbody_rgb_full (color_temperature,
+ &temp,
+ CD_COLOR_BLACKBODY_FLAG_USE_PLANCKIAN)) {
+ g_warning ("failed to get blackbody for %uK", color_temperature);
+ cd_color_rgb_set (&temp, 1.0, 1.0, 1.0);
+ } else {
+ g_debug ("using VCGT gamma of %uK = %.1f,%.1f,%.1f",
+ color_temperature, temp.R, temp.G, temp.B);
+ }
+
+ /* create array */
+ array = g_ptr_array_new_with_free_func (g_free);
+ for (i = 0; i < size; i++) {
+ in = (gdouble) i / (gdouble) (size - 1);
+ tmp = g_new0 (GnomeRROutputClutItem, 1);
+ tmp->red = cmsEvalToneCurveFloat(vcgt[0], in) * temp.R * (gdouble) 0xffff;
+ tmp->green = cmsEvalToneCurveFloat(vcgt[1], in) * temp.G * (gdouble) 0xffff;
+ tmp->blue = cmsEvalToneCurveFloat(vcgt[2], in) * temp.B * (gdouble) 0xffff;
+ g_ptr_array_add (array, tmp);
+ }
+out:
+ if (icc != NULL)
+ g_object_unref (icc);
+ return array;
+}
+
+static guint
+gnome_rr_output_get_gamma_size (GnomeRROutput *output)
+{
+ GnomeRRCrtc *crtc;
+ gint len = 0;
+
+ crtc = gnome_rr_output_get_crtc (output);
+ if (crtc == NULL)
+ return 0;
+ gnome_rr_crtc_get_gamma (crtc,
+ &len,
+ NULL, NULL, NULL);
+ return (guint) len;
+}
+
+static gboolean
+gcm_session_output_set_gamma (GnomeRROutput *output,
+ GPtrArray *array,
+ GError **error)
+{
+ gboolean ret = TRUE;
+ guint16 *red = NULL;
+ guint16 *green = NULL;
+ guint16 *blue = NULL;
+ guint i;
+ GnomeRROutputClutItem *data;
+ GnomeRRCrtc *crtc;
+
+ /* no length? */
+ if (array->len == 0) {
+ ret = FALSE;
+ g_set_error_literal (error,
+ GSD_COLOR_MANAGER_ERROR,
+ GSD_COLOR_MANAGER_ERROR_FAILED,
+ "no data in the CLUT array");
+ goto out;
+ }
+
+ /* convert to a type X understands */
+ red = g_new (guint16, array->len);
+ green = g_new (guint16, array->len);
+ blue = g_new (guint16, array->len);
+ for (i = 0; i < array->len; i++) {
+ data = g_ptr_array_index (array, i);
+ red[i] = data->red;
+ green[i] = data->green;
+ blue[i] = data->blue;
+ }
+
+ /* send to LUT */
+ crtc = gnome_rr_output_get_crtc (output);
+ if (crtc == NULL) {
+ ret = FALSE;
+ g_set_error (error,
+ GSD_COLOR_MANAGER_ERROR,
+ GSD_COLOR_MANAGER_ERROR_FAILED,
+ "failed to get ctrc for %s",
+ gnome_rr_output_get_name (output));
+ goto out;
+ }
+ gnome_rr_crtc_set_gamma (crtc, array->len,
+ red, green, blue);
+out:
+ g_free (red);
+ g_free (green);
+ g_free (blue);
+ return ret;
+}
+
+static gboolean
+gcm_session_device_set_gamma (GnomeRROutput *output,
+ CdProfile *profile,
+ guint color_temperature,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ guint size;
+ GPtrArray *clut = NULL;
+
+ /* create a lookup table */
+ size = gnome_rr_output_get_gamma_size (output);
+ if (size == 0) {
+ ret = TRUE;
+ goto out;
+ }
+ clut = gcm_session_generate_vcgt (profile, color_temperature, size);
+ if (clut == NULL) {
+ g_set_error_literal (error,
+ GSD_COLOR_MANAGER_ERROR,
+ GSD_COLOR_MANAGER_ERROR_FAILED,
+ "failed to generate vcgt");
+ goto out;
+ }
+
+ /* apply the vcgt to this output */
+ ret = gcm_session_output_set_gamma (output, clut, error);
+ if (!ret)
+ goto out;
+out:
+ if (clut != NULL)
+ g_ptr_array_unref (clut);
+ return ret;
+}
+
+static gboolean
+gcm_session_device_reset_gamma (GnomeRROutput *output,
+ guint color_temperature,
+ GError **error)
+{
+ gboolean ret;
+ guint i;
+ guint size;
+ guint32 value;
+ GPtrArray *clut;
+ GnomeRROutputClutItem *data;
+ CdColorRGB temp;
+
+ /* create a linear ramp */
+ g_debug ("falling back to dummy ramp");
+ clut = g_ptr_array_new_with_free_func (g_free);
+ size = gnome_rr_output_get_gamma_size (output);
+ if (size == 0) {
+ ret = TRUE;
+ goto out;
+ }
+
+ /* get the color temperature */
+ if (!cd_color_get_blackbody_rgb_full (color_temperature,
+ &temp,
+ CD_COLOR_BLACKBODY_FLAG_USE_PLANCKIAN)) {
+ g_warning ("failed to get blackbody for %uK", color_temperature);
+ cd_color_rgb_set (&temp, 1.0, 1.0, 1.0);
+ } else {
+ g_debug ("using reset gamma of %uK = %.1f,%.1f,%.1f",
+ color_temperature, temp.R, temp.G, temp.B);
+ }
+
+ for (i = 0; i < size; i++) {
+ value = (i * 0xffff) / (size - 1);
+ data = g_new0 (GnomeRROutputClutItem, 1);
+ data->red = value * temp.R;
+ data->green = value * temp.G;
+ data->blue = value * temp.B;
+ g_ptr_array_add (clut, data);
+ }
+
+ /* apply the vcgt to this output */
+ ret = gcm_session_output_set_gamma (output, clut, error);
+ if (!ret)
+ goto out;
+out:
+ g_ptr_array_unref (clut);
+ return ret;
+}
+
+static GnomeRROutput *
+gcm_session_get_state_output_by_id (GsdColorState *state,
+ const gchar *device_id,
+ GError **error)
+{
+ gchar *output_id;
+ GnomeRROutput *output = NULL;
+ GnomeRROutput **outputs = NULL;
+ guint i;
+
+ /* search all STATE outputs for the device id */
+ outputs = gnome_rr_screen_list_outputs (state->state_screen);
+ if (outputs == NULL) {
+ g_set_error_literal (error,
+ GSD_COLOR_MANAGER_ERROR,
+ GSD_COLOR_MANAGER_ERROR_FAILED,
+ "Failed to get outputs");
+ goto out;
+ }
+ for (i = 0; outputs[i] != NULL && output == NULL; i++) {
+ output_id = gcm_session_get_output_id (state, outputs[i]);
+ if (g_strcmp0 (output_id, device_id) == 0)
+ output = outputs[i];
+ g_free (output_id);
+ }
+ if (output == NULL) {
+ g_set_error (error,
+ GSD_COLOR_MANAGER_ERROR,
+ GSD_COLOR_MANAGER_ERROR_FAILED,
+ "Failed to find output %s",
+ device_id);
+ }
+out:
+ return output;
+}
+
+/* this function is more complicated than it should be, due to the
+ * fact that XOrg sometimes assigns no primary devices when using
+ * "xrandr --auto" or when the version of RANDR is < 1.3 */
+static gboolean
+gcm_session_use_output_profile_for_screen (GsdColorState *state,
+ GnomeRROutput *output)
+{
+ gboolean has_laptop = FALSE;
+ gboolean has_primary = FALSE;
+ GnomeRROutput **outputs;
+ GnomeRROutput *connected = NULL;
+ guint i;
+
+ /* do we have any screens marked as primary */
+ outputs = gnome_rr_screen_list_outputs (state->state_screen);
+ if (outputs == NULL || outputs[0] == NULL) {
+ g_warning ("failed to get outputs");
+ return FALSE;
+ }
+ for (i = 0; outputs[i] != NULL; i++) {
+ if (connected == NULL)
+ connected = outputs[i];
+ if (gnome_rr_output_get_is_primary (outputs[i]))
+ has_primary = TRUE;
+ if (gnome_rr_output_is_builtin_display (outputs[i]))
+ has_laptop = TRUE;
+ }
+
+ /* we have an assigned primary device, are we that? */
+ if (has_primary)
+ return gnome_rr_output_get_is_primary (output);
+
+ /* choosing the internal panel is probably sane */
+ if (has_laptop)
+ return gnome_rr_output_is_builtin_display (output);
+
+ /* we have to choose one, so go for the first connected device */
+ if (connected != NULL)
+ return gnome_rr_output_get_id (connected) == gnome_rr_output_get_id (output);
+
+ return FALSE;
+}
+
+/* TODO: remove when we can dep on a released version of colord */
+#ifndef CD_PROFILE_METADATA_SCREEN_BRIGHTNESS
+#define CD_PROFILE_METADATA_SCREEN_BRIGHTNESS "SCREEN_brightness"
+#endif
+
+#define GSD_DBUS_NAME_POWER GSD_DBUS_NAME ".Power"
+#define GSD_DBUS_INTERFACE_POWER_SCREEN GSD_DBUS_BASE_INTERFACE ".Power.Screen"
+#define GSD_DBUS_PATH_POWER GSD_DBUS_PATH "/Power"
+
+static void
+gcm_session_set_output_percentage (guint percentage)
+{
+ GDBusConnection *connection;
+
+ /* get a ref to the existing bus connection */
+ connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+ if (connection == NULL)
+ return;
+ g_dbus_connection_call (connection,
+ GSD_DBUS_NAME_POWER,
+ GSD_DBUS_PATH_POWER,
+ "org.freedesktop.DBus.Properties",
+ "Set",
+ g_variant_new_parsed ("('" GSD_DBUS_INTERFACE_POWER_SCREEN "',"
+ "'Brightness', %v)",
+ g_variant_new_int32 (percentage)),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, NULL, NULL);
+ g_object_unref (connection);
+}
+
+static void
+gcm_session_device_assign_profile_connect_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CdProfile *profile = CD_PROFILE (object);
+ const gchar *brightness_profile;
+ const gchar *filename;
+ gboolean ret;
+ GError *error = NULL;
+ GnomeRROutput *output;
+ guint brightness_percentage;
+ GcmSessionAsyncHelper *helper = (GcmSessionAsyncHelper *) user_data;
+ GsdColorState *state = GSD_COLOR_STATE (helper->state);
+
+ /* get properties */
+ ret = cd_profile_connect_finish (profile, res, &error);
+ if (!ret) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("failed to connect to profile: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ /* get the filename */
+ filename = cd_profile_get_filename (profile);
+ g_assert (filename != NULL);
+
+ /* get the output (can't save in helper as GnomeRROutput isn't
+ * a GObject, just a pointer */
+ output = gnome_rr_screen_get_output_by_id (state->state_screen,
+ helper->output_id);
+ if (output == NULL)
+ goto out;
+
+ /* if output is a laptop screen and the profile has a
+ * calibration brightness then set this new brightness */
+ brightness_profile = cd_profile_get_metadata_item (profile,
+ CD_PROFILE_METADATA_SCREEN_BRIGHTNESS);
+ if (gnome_rr_output_is_builtin_display (output) &&
+ brightness_profile != NULL) {
+ /* the percentage is stored in the profile metadata as
+ * a string, not ideal, but it's all we have... */
+ brightness_percentage = atoi (brightness_profile);
+ gcm_session_set_output_percentage (brightness_percentage);
+ }
+
+ /* set the _ICC_PROFILE atom */
+ ret = gcm_session_use_output_profile_for_screen (state, output);
+ if (ret) {
+ ret = gcm_session_screen_set_icc_profile (state,
+ filename,
+ &error);
+ if (!ret) {
+ g_warning ("failed to set screen _ICC_PROFILE: %s",
+ error->message);
+ g_clear_error (&error);
+ }
+ }
+
+ /* create a vcgt for this icc file */
+ ret = cd_profile_get_has_vcgt (profile);
+ if (ret) {
+ ret = gcm_session_device_set_gamma (output,
+ profile,
+ state->color_temperature,
+ &error);
+ if (!ret) {
+ g_warning ("failed to set %s gamma tables: %s",
+ cd_device_get_id (helper->device),
+ error->message);
+ g_error_free (error);
+ goto out;
+ }
+ } else {
+ ret = gcm_session_device_reset_gamma (output,
+ state->color_temperature,
+ &error);
+ if (!ret) {
+ g_warning ("failed to reset %s gamma tables: %s",
+ cd_device_get_id (helper->device),
+ error->message);
+ g_error_free (error);
+ goto out;
+ }
+ }
+out:
+ gcm_session_async_helper_free (helper);
+}
+
+/*
+ * Check to see if the on-disk profile has the MAPPING_device_id
+ * metadata, and if not, we should delete the profile and re-create it
+ * so that it gets mapped by the daemon.
+ */
+static gboolean
+gcm_session_check_profile_device_md (GFile *file)
+{
+ const gchar *key_we_need = CD_PROFILE_METADATA_MAPPING_DEVICE_ID;
+ CdIcc *icc;
+ gboolean ret;
+
+ icc = cd_icc_new ();
+ ret = cd_icc_load_file (icc, file, CD_ICC_LOAD_FLAGS_METADATA, NULL, NULL);
+ if (!ret)
+ goto out;
+ ret = cd_icc_get_metadata_item (icc, key_we_need) != NULL;
+ if (!ret) {
+ g_debug ("auto-edid profile is old, and contains no %s data",
+ key_we_need);
+ }
+out:
+ g_object_unref (icc);
+ return ret;
+}
+
+static void
+gcm_session_device_assign_connect_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CdDeviceKind kind;
+ CdProfile *profile = NULL;
+ gboolean ret;
+ gchar *autogen_filename = NULL;
+ gchar *autogen_path = NULL;
+ GcmEdid *edid = NULL;
+ GnomeRROutput *output = NULL;
+ GError *error = NULL;
+ GFile *file = NULL;
+ const gchar *xrandr_id;
+ GcmSessionAsyncHelper *helper;
+ CdDevice *device = CD_DEVICE (object);
+ GsdColorState *state = GSD_COLOR_STATE (user_data);
+
+ /* remove from assign array */
+ g_hash_table_remove (state->device_assign_hash,
+ cd_device_get_object_path (device));
+
+ /* get properties */
+ ret = cd_device_connect_finish (device, res, &error);
+ if (!ret) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("failed to connect to device: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ /* check we care */
+ kind = cd_device_get_kind (device);
+ if (kind != CD_DEVICE_KIND_DISPLAY)
+ goto out;
+
+ g_debug ("need to assign display device %s",
+ cd_device_get_id (device));
+
+ /* get the GnomeRROutput for the device id */
+ xrandr_id = cd_device_get_id (device);
+ output = gcm_session_get_state_output_by_id (state,
+ xrandr_id,
+ &error);
+ if (output == NULL) {
+ g_warning ("no %s device found: %s",
+ cd_device_get_id (device),
+ error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ /* create profile from device edid if it exists */
+ edid = gcm_session_get_output_edid (state, output, &error);
+ if (edid == NULL) {
+ g_warning ("unable to get EDID for %s: %s",
+ cd_device_get_id (device),
+ error->message);
+ g_clear_error (&error);
+
+ } else {
+ autogen_filename = g_strdup_printf ("edid-%s.icc",
+ gcm_edid_get_checksum (edid));
+ autogen_path = g_build_filename (g_get_user_data_dir (),
+ "icc", autogen_filename, NULL);
+
+ /* check if auto-profile has up-to-date metadata */
+ file = g_file_new_for_path (autogen_path);
+ if (gcm_session_check_profile_device_md (file)) {
+ g_debug ("auto-profile edid %s exists with md", autogen_path);
+ } else {
+ g_debug ("auto-profile edid does not exist, creating as %s",
+ autogen_path);
+
+ /* check if the system has a built-in profile */
+ ret = gnome_rr_output_is_builtin_display (output) &&
+ gcm_get_system_icc_profile (state, file);
+
+ /* try creating one from the EDID */
+ if (!ret) {
+ ret = gcm_apply_create_icc_profile_for_edid (state,
+ device,
+ edid,
+ file,
+ &error);
+ }
+
+ if (!ret) {
+ g_warning ("failed to create profile from EDID data: %s",
+ error->message);
+ g_clear_error (&error);
+ }
+ }
+ }
+
+ /* get the default profile for the device */
+ profile = cd_device_get_default_profile (device);
+ if (profile == NULL) {
+ g_debug ("%s has no default profile to set",
+ cd_device_get_id (device));
+
+ /* the default output? */
+ if (gnome_rr_output_get_is_primary (output) &&
+ state->gdk_window != NULL) {
+ gdk_property_delete (state->gdk_window,
+ gdk_atom_intern_static_string ("_ICC_PROFILE"));
+ gdk_property_delete (state->gdk_window,
+ gdk_atom_intern_static_string ("_ICC_PROFILE_IN_X_VERSION"));
+ }
+
+ /* reset, as we want linear profiles for profiling */
+ ret = gcm_session_device_reset_gamma (output,
+ state->color_temperature,
+ &error);
+ if (!ret) {
+ g_warning ("failed to reset %s gamma tables: %s",
+ cd_device_get_id (device),
+ error->message);
+ g_error_free (error);
+ goto out;
+ }
+ goto out;
+ }
+
+ /* get properties */
+ helper = g_new0 (GcmSessionAsyncHelper, 1);
+ helper->output_id = gnome_rr_output_get_id (output);
+ helper->state = g_object_ref (state);
+ helper->device = g_object_ref (device);
+ cd_profile_connect (profile,
+ state->cancellable,
+ gcm_session_device_assign_profile_connect_cb,
+ helper);
+out:
+ g_free (autogen_filename);
+ g_free (autogen_path);
+ if (file != NULL)
+ g_object_unref (file);
+ if (edid != NULL)
+ g_object_unref (edid);
+ if (profile != NULL)
+ g_object_unref (profile);
+}
+
+static void
+gcm_session_device_assign (GsdColorState *state, CdDevice *device)
+{
+ const gchar *key;
+ gpointer found;
+
+ /* are we already assigning this device */
+ key = cd_device_get_object_path (device);
+ found = g_hash_table_lookup (state->device_assign_hash, key);
+ if (found != NULL) {
+ g_debug ("assign for %s already in progress", key);
+ return;
+ }
+ g_hash_table_insert (state->device_assign_hash,
+ g_strdup (key),
+ GINT_TO_POINTER (TRUE));
+ cd_device_connect (device,
+ state->cancellable,
+ gcm_session_device_assign_connect_cb,
+ state);
+}
+
+static void
+gcm_session_device_added_assign_cb (CdClient *client,
+ CdDevice *device,
+ GsdColorState *state)
+{
+ gcm_session_device_assign (state, device);
+}
+
+static void
+gcm_session_device_changed_assign_cb (CdClient *client,
+ CdDevice *device,
+ GsdColorState *state)
+{
+ g_debug ("%s changed", cd_device_get_object_path (device));
+ gcm_session_device_assign (state, device);
+}
+
+static void
+gcm_session_create_device_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CdDevice *device;
+ GError *error = NULL;
+
+ device = cd_client_create_device_finish (CD_CLIENT (object),
+ res,
+ &error);
+ if (device == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
+ !g_error_matches (error, CD_CLIENT_ERROR, CD_CLIENT_ERROR_ALREADY_EXISTS))
+ g_warning ("failed to create device: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+ g_object_unref (device);
+}
+
+static void
+gcm_session_add_state_output (GsdColorState *state, GnomeRROutput *output)
+{
+ const gchar *edid_checksum = NULL;
+ const gchar *model = NULL;
+ const gchar *output_name = NULL;
+ const gchar *serial = NULL;
+ const gchar *vendor = NULL;
+ gboolean ret;
+ gchar *device_id = NULL;
+ GcmEdid *edid;
+ GError *error = NULL;
+ GHashTable *device_props = NULL;
+
+ /* VNC creates a fake device that cannot be color managed */
+ output_name = gnome_rr_output_get_name (output);
+ if (output_name != NULL && g_str_has_prefix (output_name, "VNC-")) {
+ g_debug ("ignoring %s as fake VNC device detected", output_name);
+ return;
+ }
+
+ /* try to get edid */
+ edid = gcm_session_get_output_edid (state, output, &error);
+ if (edid == NULL) {
+ g_warning ("failed to get edid: %s",
+ error->message);
+ g_clear_error (&error);
+ }
+
+ /* prefer DMI data for the internal output */
+ ret = gnome_rr_output_is_builtin_display (output);
+ if (ret) {
+ model = cd_client_get_system_model (state->client);
+ vendor = cd_client_get_system_vendor (state->client);
+ }
+
+ /* use EDID data if we have it */
+ if (edid != NULL) {
+ edid_checksum = gcm_edid_get_checksum (edid);
+ if (model == NULL)
+ model = gcm_edid_get_monitor_name (edid);
+ if (vendor == NULL)
+ vendor = gcm_edid_get_vendor_name (edid);
+ if (serial == NULL)
+ serial = gcm_edid_get_serial_number (edid);
+ }
+
+ /* ensure mandatory fields are set */
+ if (model == NULL)
+ model = gnome_rr_output_get_name (output);
+ if (vendor == NULL)
+ vendor = "unknown";
+ if (serial == NULL)
+ serial = "unknown";
+
+ device_id = gcm_session_get_output_id (state, output);
+ g_debug ("output %s added", device_id);
+ device_props = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, NULL);
+ g_hash_table_insert (device_props,
+ (gpointer) CD_DEVICE_PROPERTY_KIND,
+ (gpointer) cd_device_kind_to_string (CD_DEVICE_KIND_DISPLAY));
+ g_hash_table_insert (device_props,
+ (gpointer) CD_DEVICE_PROPERTY_MODE,
+ (gpointer) cd_device_mode_to_string (CD_DEVICE_MODE_PHYSICAL));
+ g_hash_table_insert (device_props,
+ (gpointer) CD_DEVICE_PROPERTY_COLORSPACE,
+ (gpointer) cd_colorspace_to_string (CD_COLORSPACE_RGB));
+ g_hash_table_insert (device_props,
+ (gpointer) CD_DEVICE_PROPERTY_VENDOR,
+ (gpointer) vendor);
+ g_hash_table_insert (device_props,
+ (gpointer) CD_DEVICE_PROPERTY_MODEL,
+ (gpointer) model);
+ g_hash_table_insert (device_props,
+ (gpointer) CD_DEVICE_PROPERTY_SERIAL,
+ (gpointer) serial);
+ g_hash_table_insert (device_props,
+ (gpointer) CD_DEVICE_METADATA_XRANDR_NAME,
+ (gpointer) gnome_rr_output_get_name (output));
+ g_hash_table_insert (device_props,
+ (gpointer) CD_DEVICE_METADATA_OUTPUT_PRIORITY,
+ gnome_rr_output_get_is_primary (output) ?
+ (gpointer) CD_DEVICE_METADATA_OUTPUT_PRIORITY_PRIMARY :
+ (gpointer) CD_DEVICE_METADATA_OUTPUT_PRIORITY_SECONDARY);
+ if (edid_checksum != NULL) {
+ g_hash_table_insert (device_props,
+ (gpointer) CD_DEVICE_METADATA_OUTPUT_EDID_MD5,
+ (gpointer) edid_checksum);
+ }
+ /* set this so we can call the device a 'Laptop Screen' in the
+ * control center main panel */
+ if (gnome_rr_output_is_builtin_display (output)) {
+ g_hash_table_insert (device_props,
+ (gpointer) CD_DEVICE_PROPERTY_EMBEDDED,
+ NULL);
+ }
+ cd_client_create_device (state->client,
+ device_id,
+ CD_OBJECT_SCOPE_TEMP,
+ device_props,
+ state->cancellable,
+ gcm_session_create_device_cb,
+ state);
+ g_free (device_id);
+ if (device_props != NULL)
+ g_hash_table_unref (device_props);
+ if (edid != NULL)
+ g_object_unref (edid);
+}
+
+
+static void
+gnome_rr_screen_output_added_cb (GnomeRRScreen *screen,
+ GnomeRROutput *output,
+ GsdColorState *state)
+{
+ gcm_session_add_state_output (state, output);
+}
+
+static void
+gcm_session_screen_removed_delete_device_cb (GObject *object, GAsyncResult *res, gpointer user_data)
+{
+ gboolean ret;
+ GError *error = NULL;
+
+ /* deleted device */
+ ret = cd_client_delete_device_finish (CD_CLIENT (object),
+ res,
+ &error);
+ if (!ret) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("failed to delete device: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+gcm_session_screen_removed_find_device_cb (GObject *object, GAsyncResult *res, gpointer user_data)
+{
+ GError *error = NULL;
+ CdDevice *device;
+ GsdColorState *state = GSD_COLOR_STATE (user_data);
+
+ device = cd_client_find_device_finish (state->client,
+ res,
+ &error);
+ if (device == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("failed to find device: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+ g_debug ("output %s found, and will be removed",
+ cd_device_get_object_path (device));
+ cd_client_delete_device (state->client,
+ device,
+ state->cancellable,
+ gcm_session_screen_removed_delete_device_cb,
+ state);
+ g_object_unref (device);
+}
+
+static void
+gnome_rr_screen_output_removed_cb (GnomeRRScreen *screen,
+ GnomeRROutput *output,
+ GsdColorState *state)
+{
+ g_debug ("output %s removed",
+ gnome_rr_output_get_name (output));
+ g_hash_table_remove (state->edid_cache,
+ gnome_rr_output_get_name (output));
+ cd_client_find_device_by_property (state->client,
+ CD_DEVICE_METADATA_XRANDR_NAME,
+ gnome_rr_output_get_name (output),
+ state->cancellable,
+ gcm_session_screen_removed_find_device_cb,
+ state);
+}
+
+static void
+gcm_session_get_devices_cb (GObject *object, GAsyncResult *res, gpointer user_data)
+{
+ CdDevice *device;
+ GError *error = NULL;
+ GPtrArray *array;
+ guint i;
+ GsdColorState *state = GSD_COLOR_STATE (user_data);
+
+ array = cd_client_get_devices_finish (CD_CLIENT (object), res, &error);
+ if (array == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("failed to get devices: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+ for (i = 0; i < array->len; i++) {
+ device = g_ptr_array_index (array, i);
+ gcm_session_device_assign (state, device);
+ }
+
+ if (array != NULL)
+ g_ptr_array_unref (array);
+}
+
+static void
+gcm_session_profile_gamma_find_device_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ CdClient *client = CD_CLIENT (object);
+ CdDevice *device = NULL;
+ GError *error = NULL;
+ GsdColorState *state = GSD_COLOR_STATE (user_data);
+
+ device = cd_client_find_device_by_property_finish (client,
+ res,
+ &error);
+ if (device == NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("could not find device: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ /* get properties */
+ cd_device_connect (device,
+ state->cancellable,
+ gcm_session_device_assign_connect_cb,
+ state);
+
+ if (device != NULL)
+ g_object_unref (device);
+}
+
+static void
+gcm_session_set_gamma_for_all_devices (GsdColorState *state)
+{
+ GnomeRROutput **outputs;
+ guint i;
+
+ /* setting the temperature before we get the list of devices is fine,
+ * as we use the temperature in the calculation */
+ if (state->state_screen == NULL)
+ return;
+
+ /* get STATE outputs */
+ outputs = gnome_rr_screen_list_outputs (state->state_screen);
+ if (outputs == NULL) {
+ g_warning ("failed to get outputs");
+ return;
+ }
+ for (i = 0; outputs[i] != NULL; i++) {
+ /* get CdDevice for this output */
+ cd_client_find_device_by_property (state->client,
+ CD_DEVICE_METADATA_XRANDR_NAME,
+ gnome_rr_output_get_name (outputs[i]),
+ state->cancellable,
+ gcm_session_profile_gamma_find_device_cb,
+ state);
+ }
+}
+
+/* We have to reset the gamma tables each time as if the primary output
+ * has changed then different crtcs are going to be used.
+ * See https://bugzilla.gnome.org/show_bug.cgi?id=660164 for an example */
+static void
+gnome_rr_screen_output_changed_cb (GnomeRRScreen *screen,
+ GsdColorState *state)
+{
+ gcm_session_set_gamma_for_all_devices (state);
+}
+
+static gboolean
+has_changed (char **strv,
+ const char *str)
+{
+ guint i;
+ for (i = 0; strv[i] != NULL; i++) {
+ if (g_str_equal (str, strv[i]))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+gcm_session_active_changed_cb (GDBusProxy *session,
+ GVariant *changed,
+ char **invalidated,
+ GsdColorState *state)
+{
+ GVariant *active_v = NULL;
+ gboolean is_active;
+
+ if (has_changed (invalidated, "SessionIsActive"))
+ return;
+
+ /* not yet connected to the daemon */
+ if (!cd_client_get_connected (state->client))
+ return;
+
+ active_v = g_dbus_proxy_get_cached_property (session, "SessionIsActive");
+ g_return_if_fail (active_v != NULL);
+ is_active = g_variant_get_boolean (active_v);
+ g_variant_unref (active_v);
+
+ /* When doing the fast-user-switch into a new account, load the
+ * new users chosen profiles.
+ *
+ * If this is the first time the GnomeSettingsSession has been
+ * loaded, then we'll get a change from unknown to active
+ * and we want to avoid reprobing the devices for that.
+ */
+ if (is_active && !state->session_is_active) {
+ g_debug ("Done switch to new account, reload devices");
+ cd_client_get_devices (state->client,
+ state->cancellable,
+ gcm_session_get_devices_cb,
+ state);
+ }
+ state->session_is_active = is_active;
+}
+
+static void
+gcm_session_client_connect_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ gboolean ret;
+ GError *error = NULL;
+ GnomeRROutput **outputs;
+ guint i;
+ GsdColorState *state = GSD_COLOR_STATE (user_data);
+
+ /* connected */
+ ret = cd_client_connect_finish (state->client, res, &error);
+ if (!ret) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("failed to connect to colord: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ /* is there an available colord instance? */
+ ret = cd_client_get_has_server (state->client);
+ if (!ret) {
+ g_warning ("There is no colord server available");
+ return;
+ }
+
+ /* watch if sessions change */
+ g_signal_connect_object (state->session, "g-properties-changed",
+ G_CALLBACK (gcm_session_active_changed_cb),
+ state, 0);
+
+ /* add screens */
+ gnome_rr_screen_refresh (state->state_screen, &error);
+ if (error != NULL) {
+ g_warning ("failed to refresh: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ /* get STATE outputs */
+ outputs = gnome_rr_screen_list_outputs (state->state_screen);
+ if (outputs == NULL) {
+ g_warning ("failed to get outputs");
+ return;
+ }
+ for (i = 0; outputs[i] != NULL; i++) {
+ gcm_session_add_state_output (state, outputs[i]);
+ }
+
+ /* only connect when colord is awake */
+ g_signal_connect (state->state_screen, "output-connected",
+ G_CALLBACK (gnome_rr_screen_output_added_cb),
+ state);
+ g_signal_connect (state->state_screen, "output-disconnected",
+ G_CALLBACK (gnome_rr_screen_output_removed_cb),
+ state);
+ g_signal_connect (state->state_screen, "changed",
+ G_CALLBACK (gnome_rr_screen_output_changed_cb),
+ state);
+
+ g_signal_connect (state->client, "device-added",
+ G_CALLBACK (gcm_session_device_added_assign_cb),
+ state);
+ g_signal_connect (state->client, "device-changed",
+ G_CALLBACK (gcm_session_device_changed_assign_cb),
+ state);
+
+ /* set for each device that already exist */
+ cd_client_get_devices (state->client,
+ state->cancellable,
+ gcm_session_get_devices_cb,
+ state);
+}
+
+static void
+on_rr_screen_acquired (GObject *object,
+ GAsyncResult *result,
+ gpointer data)
+{
+ GsdColorState *state = data;
+ GnomeRRScreen *screen;
+ GError *error = NULL;
+
+ /* gnome_rr_screen_new_async() does not take a GCancellable */
+ if (g_cancellable_is_cancelled (state->cancellable))
+ goto out;
+
+ screen = gnome_rr_screen_new_finish (result, &error);
+ if (screen == NULL) {
+ g_warning ("failed to get screens: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ state->state_screen = screen;
+
+ cd_client_connect (state->client,
+ state->cancellable,
+ gcm_session_client_connect_cb,
+ state);
+out:
+ /* manually added */
+ g_object_unref (state);
+}
+
+void
+gsd_color_state_start (GsdColorState *state)
+{
+ /* use a fresh cancellable for each start->stop operation */
+ g_cancellable_cancel (state->cancellable);
+ g_clear_object (&state->cancellable);
+ state->cancellable = g_cancellable_new ();
+
+ /* coldplug the list of screens */
+ gnome_rr_screen_new_async (gdk_screen_get_default (),
+ on_rr_screen_acquired,
+ g_object_ref (state));
+}
+
+void
+gsd_color_state_stop (GsdColorState *state)
+{
+ g_cancellable_cancel (state->cancellable);
+}
+
+static void
+gsd_color_state_class_init (GsdColorStateClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gsd_color_state_finalize;
+}
+
+static void
+gsd_color_state_init (GsdColorState *state)
+{
+ /* track the active session */
+ state->session = gnome_settings_bus_get_session_proxy ();
+
+#ifdef GDK_WINDOWING_X11
+ /* set the _ICC_PROFILE atoms on the root screen */
+ if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
+ state->gdk_window = gdk_screen_get_root_window (gdk_screen_get_default ());
+#endif
+
+ /* parsing the EDID is expensive */
+ state->edid_cache = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ g_object_unref);
+
+ /* we don't want to assign devices multiple times at startup */
+ state->device_assign_hash = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ NULL);
+
+ /* default color temperature */
+ state->color_temperature = GSD_COLOR_TEMPERATURE_DEFAULT;
+
+ state->client = cd_client_new ();
+}
+
+static void
+gsd_color_state_finalize (GObject *object)
+{
+ GsdColorState *state;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GSD_IS_COLOR_STATE (object));
+
+ state = GSD_COLOR_STATE (object);
+
+ g_cancellable_cancel (state->cancellable);
+ g_clear_object (&state->cancellable);
+ g_clear_object (&state->client);
+ g_clear_object (&state->session);
+ g_clear_pointer (&state->edid_cache, g_hash_table_destroy);
+ g_clear_pointer (&state->device_assign_hash, g_hash_table_destroy);
+ g_clear_object (&state->state_screen);
+
+ G_OBJECT_CLASS (gsd_color_state_parent_class)->finalize (object);
+}
+
+GsdColorState *
+gsd_color_state_new (void)
+{
+ GsdColorState *state;
+ state = g_object_new (GSD_TYPE_COLOR_STATE, NULL);
+ return GSD_COLOR_STATE (state);
+}