summaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
authorRichard Hughes <richard@hughsie.com>2012-12-16 23:14:42 +0000
committerRichard Hughes <richard@hughsie.com>2012-12-17 09:20:45 +0000
commit6d04f27d025165f6ba9957394b2c1c2bfaeb5374 (patch)
treebb039191fabe3764aa09ec77254631bc3fea159d /contrib
parentb6cc6f6dcb1e2e594c6f338ee08cb1289bc8b83f (diff)
downloadcolord-6d04f27d025165f6ba9957394b2c1c2bfaeb5374.tar.gz
Add a session helper that can be used to calibrate the screen
This is an optional D-Bus session activated service and designed to be used by GNOME and KDE for display calibration. The D-Bus API should be regarded as experimental and should not be considered stable like the colord daemon D-Bus API. An example client program is also included, although is disabled by default as it requires GNOME libraries like libgnomedesktop and also requires colord-gtk which would both create a dependancy loop for projects like jhbuild.
Diffstat (limited to 'contrib')
-rw-r--r--contrib/Makefile.am4
-rw-r--r--contrib/colord.spec.in5
-rw-r--r--contrib/session-helper/Makefile.am83
-rw-r--r--contrib/session-helper/cd-example.c710
-rw-r--r--contrib/session-helper/cd-example.ui124
-rw-r--r--contrib/session-helper/cd-main.c2282
-rw-r--r--contrib/session-helper/cd-state.c691
-rw-r--r--contrib/session-helper/cd-state.h99
-rw-r--r--contrib/session-helper/org.freedesktop.ColorHelper.service.in3
-rw-r--r--contrib/session-helper/org.freedesktop.ColorHelper.xml249
10 files changed, 4250 insertions, 0 deletions
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
index ae44c94..f3e7ed1 100644
--- a/contrib/Makefile.am
+++ b/contrib/Makefile.am
@@ -1 +1,5 @@
+
+SUBDIRS = \
+ session-helper
+
-include $(top_srcdir)/git.mk
diff --git a/contrib/colord.spec.in b/contrib/colord.spec.in
index 1ba421c..525216a 100644
--- a/contrib/colord.spec.in
+++ b/contrib/colord.spec.in
@@ -115,6 +115,11 @@ exit 0
/usr/lib/systemd/system/colord.service
%{_sysconfdir}/bash_completion.d/*-completion.bash
+# session helper
+%{_libexecdir}/colord-session
+%{_datadir}/dbus-1/interfaces/org.freedesktop.ColorHelper.xml
+%{_datadir}/dbus-1/services/org.freedesktop.ColorHelper.service
+
%files devel
%defattr(-,root,root,-)
%{_includedir}/colord-1
diff --git a/contrib/session-helper/Makefile.am b/contrib/session-helper/Makefile.am
new file mode 100644
index 0000000..e58706e
--- /dev/null
+++ b/contrib/session-helper/Makefile.am
@@ -0,0 +1,83 @@
+introspectiondir = $(datadir)/dbus-1/interfaces
+dist_introspection_DATA = \
+ org.freedesktop.ColorHelper.xml
+
+INCLUDES = \
+ $(COLORD_GTK_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(GNOME_DESKTOP_CFLAGS) \
+ $(LCMS_CFLAGS) \
+ -I$(top_srcdir)/client \
+ -I$(top_srcdir)/libcolord \
+ -I$(top_srcdir)/src \
+ -DCD_COMPILATION \
+ -DG_LOG_DOMAIN=\"Cd\" \
+ -DLIBEXECDIR=\"$(libexecdir)\" \
+ -DLIBDIR=\"$(libdir)\" \
+ -DDATADIR=\"$(datadir)\" \
+ -DSYSCONFDIR=\""$(sysconfdir)"\" \
+ -DLOCALSTATEDIR=\""$(localstatedir)"\" \
+ -DVERSION="\"$(VERSION)\""
+
+COLORD_LIBS = \
+ $(top_builddir)/libcolord/libcolord.la
+
+libexec_PROGRAMS = \
+ colord-session
+
+colord_session_SOURCES = \
+ $(top_srcdir)/client/cd-lcms-helpers.c \
+ $(top_srcdir)/client/cd-lcms-helpers.h \
+ $(top_srcdir)/src/cd-debug.c \
+ $(top_srcdir)/src/cd-debug.h \
+ cd-state.c \
+ cd-state.h \
+ cd-main.c
+
+colord_session_LDADD = \
+ $(COLORD_LIBS) \
+ $(LCMS_LIBS) \
+ $(GLIB_LIBS) \
+ -lm
+
+colord_session_CFLAGS = \
+ $(WARNINGFLAGS_C)
+
+dbusservicemaindir = $(datadir)/dbus-1/services
+dbusservicemain_in_files = org.freedesktop.ColorHelper.service.in
+dbusservicemain_DATA = $(dbusservicemain_in_files:.service.in=.service)
+$(dbusservicemain_DATA): $(dbusservicemain_in_files) Makefile
+ @sed -e "s|\@servicedir\@|$(libexecdir)|" $< | \
+ sed -e "s|\@daemon_user\@|$(daemon_user)|" > $@
+
+if CD_BUILD_SESSION_EXAMPLE
+noinst_PROGRAMS = \
+ colord-session-example
+colord_session_example_SOURCES = \
+ cd-example.c
+colord_session_example_LDADD = \
+ $(COLORD_GTK_LIBS) \
+ $(COLORD_LIBS) \
+ $(GLIB_LIBS) \
+ $(GNOME_DESKTOP_LIBS) \
+ $(LCMS_LIBS) -lm
+colord_session_example_CFLAGS = \
+ $(WARNINGFLAGS_C)
+
+test: colord-session-example
+ ./colord-session-example \
+ --device "xrandr-Lenovo Group Limited" \
+ --sensor dummy \
+ --title "This is a test profile" \
+ --quality high \
+ --whitepoint 0
+
+endif
+
+EXTRA_DIST = \
+ $(dbusservicemain_in_files)
+
+DISTCLEANFILES = \
+ $(dbusservicemain_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/contrib/session-helper/cd-example.c b/contrib/session-helper/cd-example.c
new file mode 100644
index 0000000..203173f
--- /dev/null
+++ b/contrib/session-helper/cd-example.c
@@ -0,0 +1,710 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 Richard Hughes <richard@hughsie.com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <colord-gtk.h>
+#include <gio/gio.h>
+#include <glib.h>
+#include <math.h>
+#include <stdlib.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-rr.h>
+
+typedef struct {
+ GnomeRROutput *output;
+ GnomeRRScreen *x11_screen;
+ guint gamma_size;
+ GMainLoop *loop;
+ GtkWidget *sample_widget;
+ GtkBuilder *builder;
+ CdDevice *device;
+ GDBusProxy *proxy;
+} CdExamplePrivate;
+
+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;
+}
+
+/**
+ * cd_example_calib_setup_screen:
+ **/
+static gboolean
+cd_example_calib_setup_screen (CdExamplePrivate *priv,
+ const gchar *name,
+ GError **error)
+{
+ gboolean ret = TRUE;
+
+ /* get screen */
+ priv->x11_screen = gnome_rr_screen_new (gdk_screen_get_default (), error);
+ if (priv->x11_screen == NULL) {
+ ret = FALSE;
+ goto out;
+ }
+
+ /* get the output */
+ priv->output = gnome_rr_screen_get_output_by_name (priv->x11_screen,
+ name);
+ if (priv->output == NULL) {
+ ret = FALSE;
+ g_set_error_literal (error, 1, 0,
+ "failed to get output");
+ goto out;
+ }
+
+ /* create a lookup table */
+ priv->gamma_size = gnome_rr_output_get_gamma_size (priv->output);
+ if (priv->gamma_size == 0) {
+ ret = FALSE;
+ g_set_error_literal (error, 1, 0,
+ "gamma size is zero");
+ goto out;
+ }
+out:
+ return ret;
+}
+
+/**
+ * cd_example_calib_set_output_gamma:
+ **/
+static gboolean
+cd_example_calib_set_output_gamma (CdExamplePrivate *priv,
+ GPtrArray *array,
+ GError **error)
+{
+ CdColorRGB *p1;
+ CdColorRGB *p2;
+ CdColorRGB result;
+ gboolean ret = TRUE;
+ gdouble mix;
+ GnomeRRCrtc *crtc;
+ guint16 *blue = NULL;
+ guint16 *green = NULL;
+ guint16 *red = NULL;
+ guint i;
+
+ /* no length? */
+ if (array->len == 0) {
+ ret = FALSE;
+ g_set_error_literal (error, 1, 0,
+ "no data in the CLUT array");
+ goto out;
+ }
+
+ /* convert to a type X understands of the right size */
+ red = g_new (guint16, priv->gamma_size);
+ green = g_new (guint16, priv->gamma_size);
+ blue = g_new (guint16, priv->gamma_size);
+ cd_color_set_rgb (&result, 1.0, 1.0, 1.0);
+ for (i = 0; i < priv->gamma_size; i++) {
+ mix = (gdouble) (array->len - 1) /
+ (gdouble) (priv->gamma_size - 1) *
+ (gdouble) i;
+ p1 = g_ptr_array_index (array, (guint) floor (mix));
+ p2 = g_ptr_array_index (array, (guint) ceil (mix));
+ cd_color_rgb_interpolate (p1,
+ p2,
+ mix - (gint) mix,
+ &result);
+ red[i] = result.R * 0xffff;
+ green[i] = result.G * 0xffff;
+ blue[i] = result.B * 0xffff;
+ }
+
+ /* send to LUT */
+ crtc = gnome_rr_output_get_crtc (priv->output);
+ if (crtc == NULL) {
+ ret = FALSE;
+ g_set_error (error, 1, 0,
+ "failed to get ctrc for %s",
+ gnome_rr_output_get_name (priv->output));
+ goto out;
+ }
+ gnome_rr_crtc_set_gamma (crtc, priv->gamma_size,
+ red, green, blue);
+out:
+ g_free (red);
+ g_free (green);
+ g_free (blue);
+ return ret;
+}
+
+/**
+ * cd_example_property_changed_cb:
+ **/
+static void
+cd_example_property_changed_cb (GDBusProxy *proxy,
+ GVariant *changed_properties,
+ GStrv invalidated_properties,
+ CdExamplePrivate *priv)
+{
+ const gchar *key;
+ GVariantIter *iter;
+ GVariant *value;
+ GtkWidget *widget;
+
+ if (g_variant_n_children (changed_properties) > 0) {
+ g_variant_get (changed_properties,
+ "a{sv}",
+ &iter);
+ while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) {
+ if (g_strcmp0 (key, "Progress") == 0) {
+ widget = GTK_WIDGET (gtk_builder_get_object (priv->builder,
+ "progressbar_status"));
+ gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (widget),
+ g_variant_get_uint32 (value) / 100.0f);
+ }
+ }
+ g_variant_iter_free (iter);
+ }
+}
+
+typedef enum {
+ CD_MAIN_INTERACTION_CODE_ATTACH_TO_SCREEN,
+ CD_MAIN_INTERACTION_CODE_MOVE_TO_CALIBRATION,
+ CD_MAIN_INTERACTION_CODE_MOVE_TO_SURFACE,
+ CD_MAIN_INTERACTION_CODE_NONE
+} CdMainInteractionCode;
+
+/**
+ * cd_example_interaction_required:
+ **/
+static void
+cd_example_interaction_required (CdExamplePrivate *priv,
+ CdMainInteractionCode code,
+ const gchar *message,
+ const gchar *image)
+{
+ GtkLabel *label;
+ GtkImage *img;
+ GdkPixbuf *pixbuf;
+
+ /* set image */
+ img = GTK_IMAGE (gtk_builder_get_object (priv->builder,
+ "image_status"));
+ if (image != NULL && image[0] != '\0') {
+ g_debug ("showing image %s", image);
+ pixbuf = gdk_pixbuf_new_from_file_at_size (image,
+ 400, 400,
+ NULL);
+ if (pixbuf != NULL) {
+ gtk_image_set_from_pixbuf (img, pixbuf);
+ g_object_unref (pixbuf);
+ }
+ gtk_widget_set_visible (GTK_WIDGET (img), TRUE);
+ gtk_widget_set_visible (GTK_WIDGET (priv->sample_widget), FALSE);
+ } else {
+ g_debug ("hiding image");
+ gtk_widget_set_visible (GTK_WIDGET (img), FALSE);
+ gtk_widget_set_visible (GTK_WIDGET (priv->sample_widget), TRUE);
+ }
+ label = GTK_LABEL (gtk_builder_get_object (priv->builder,
+ "label_status"));
+ gtk_label_set_label (label, message);
+}
+
+/**
+ * cd_example_signal_cb:
+ **/
+static void
+cd_example_signal_cb (GDBusProxy *proxy,
+ const gchar *sender_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ CdExamplePrivate *priv)
+{
+ CdColorRGB color;
+ CdColorRGB *color_tmp;
+ CdMainInteractionCode code;
+ const gchar *image;
+ const gchar *message;
+ const gchar *str;
+ gboolean ret;
+ GError *error = NULL;
+ GPtrArray *array = NULL;
+ GtkImage *img;
+ GtkLabel *label;
+ GVariantIter *iter;
+
+ if (g_strcmp0 (signal_name, "Finished") == 0) {
+ g_variant_get (parameters, "(u&s)",
+ &code,
+ &str);
+ if (code == 0) {
+ g_debug ("calibration succeeded");
+ } else {
+ g_warning ("calibration failed with code %i: %s",
+ code, str);
+ }
+ g_main_loop_quit (priv->loop);
+ goto out;
+ }
+ if (g_strcmp0 (signal_name, "UpdateSample") == 0) {
+ g_variant_get (parameters, "(ddd)",
+ &color.R,
+ &color.G,
+ &color.B);
+ img = GTK_IMAGE (gtk_builder_get_object (priv->builder,
+ "image_status"));
+ gtk_widget_set_visible (GTK_WIDGET (img), FALSE);
+ gtk_widget_set_visible (GTK_WIDGET (priv->sample_widget), TRUE);
+ cd_sample_widget_set_color (CD_SAMPLE_WIDGET (priv->sample_widget),
+ &color);
+ /* set the generic label too */
+ label = GTK_LABEL (gtk_builder_get_object (priv->builder,
+ "label_status"));
+ gtk_label_set_label (label, "Do not disturb the calibration device while in progress");
+ goto out;
+ }
+ if (g_strcmp0 (signal_name, "InteractionRequired") == 0) {
+ g_variant_get (parameters, "(u&s&s)",
+ &code,
+ &message,
+ &image);
+ g_print ("Interaction required type %i: %s\n",
+ code, message);
+ cd_example_interaction_required (priv,
+ code,
+ message,
+ image);
+ goto out;
+ }
+ if (g_strcmp0 (signal_name, "UpdateGamma") == 0) {
+ g_variant_get (parameters,
+ "(a(ddd))",
+ &iter);
+ array = g_ptr_array_new_with_free_func (g_free);
+ while (g_variant_iter_loop (iter, "(ddd)",
+ &color.R,
+ &color.G,
+ &color.B)) {
+ color_tmp = cd_color_rgb_new ();
+ cd_color_copy_rgb (&color, color_tmp);
+ g_ptr_array_add (array, color_tmp);
+ }
+ g_variant_iter_free (iter);
+ ret = cd_example_calib_set_output_gamma (priv,
+ array,
+ &error);
+ if (!ret) {
+ g_warning ("failed to update gamma: %s",
+ error->message);
+ g_error_free (error);
+ goto out;
+ }
+ goto out;
+ }
+ g_warning ("got unknown signal %s", signal_name);
+out:
+ return;
+}
+
+/**
+ * cd_example_move_and_resize_window:
+ **/
+static gboolean
+cd_example_move_and_resize_window (GtkWindow *window,
+ CdDevice *device,
+ GError **error)
+{
+ const gchar *xrandr_name;
+ gboolean ret = TRUE;
+ gchar *plug_name;
+ GdkRectangle rect;
+ GdkScreen *screen;
+ gint i;
+ gint monitor_num = -1;
+ gint num_monitors;
+
+ /* find the monitor num of the device output */
+ screen = gdk_screen_get_default ();
+ num_monitors = gdk_screen_get_n_monitors (screen);
+ xrandr_name = cd_device_get_metadata_item (device,
+ CD_DEVICE_METADATA_XRANDR_NAME);
+ for (i = 0; i < num_monitors; i++) {
+ plug_name = gdk_screen_get_monitor_plug_name (screen, i);
+ if (g_strcmp0 (plug_name, xrandr_name) == 0)
+ monitor_num = i;
+ g_free (plug_name);
+ }
+ if (monitor_num == -1) {
+ ret = FALSE;
+ g_set_error (error, 1, 0,
+ "failed to find output %s",
+ xrandr_name);
+ goto out;
+ }
+
+ /* move the window, and set it to the right size */
+ gdk_screen_get_monitor_geometry (screen, monitor_num, &rect);
+ gtk_window_move (window, rect.x, rect.y);
+ gtk_window_set_default_size (window, rect.width, rect.height);
+ g_debug ("Setting window to %ix%i with size %ix%i",
+ rect.x, rect.y, rect.width, rect.height);
+out:
+ return ret;
+}
+
+/**
+ * cd_example_window_realize_cb:
+ **/
+static void
+cd_example_window_realize_cb (GtkWidget *widget, CdExamplePrivate *priv)
+{
+ gtk_window_fullscreen (GTK_WINDOW (widget));
+}
+
+/**
+ * cd_example_window_realize_cb:
+ **/
+static gboolean
+cd_example_window_state_cb (GtkWidget *widget,
+ GdkEvent *event,
+ CdExamplePrivate *priv)
+{
+ gboolean ret;
+ GError *error = NULL;
+ GdkEventWindowState *event_state = (GdkEventWindowState *) event;
+ GtkWindow *window = GTK_WINDOW (widget);
+
+ /* check event */
+ if (event->type != GDK_WINDOW_STATE)
+ return TRUE;
+ if (event_state->changed_mask != GDK_WINDOW_STATE_FULLSCREEN)
+ return TRUE;
+
+ /* resize to the correct screen */
+ ret = cd_example_move_and_resize_window (window,
+ priv->device,
+ &error);
+
+ if (!ret) {
+ g_warning ("Failed to resize window: %s", error->message);
+ g_error_free (error);
+ }
+ return TRUE;
+}
+
+/**
+ * cd_example_button_start_cb:
+ **/
+static void
+cd_example_button_start_cb (GtkWidget *widget, CdExamplePrivate *priv)
+{
+ GVariant *retval;
+ GError *error = NULL;
+
+ /* continue */
+ retval = g_dbus_proxy_call_sync (priv->proxy,
+ "Resume",
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (retval == NULL) {
+ g_warning ("Failed to send Resume: %s", error->message);
+ goto out;
+ }
+out:
+ if (retval != NULL)
+ g_variant_unref (retval);
+}
+
+/**
+ * cd_example_button_cancel_cb:
+ **/
+static void
+cd_example_button_cancel_cb (GtkWidget *widget, CdExamplePrivate *priv)
+{
+ g_main_loop_quit (priv->loop);
+}
+
+/**
+ * main:
+ **/
+int
+main (int argc, char **argv)
+{
+ CdClient *client = NULL;
+ CdExamplePrivate *priv = NULL;
+ const gchar *name;
+ gboolean ret;
+ gchar *device_id = NULL;
+ gchar *quality = NULL;
+ gchar *sensor_id = NULL;
+ gchar *title = NULL;
+ GDBusConnection *connection = NULL;
+ GError *error = NULL;
+ gint retval = EXIT_FAILURE;
+ GOptionContext *context;
+ GtkBox *box;
+ GtkSettings *settings;
+ GtkWidget *widget;
+ GtkWindow *window;
+ guint quality_value = 0;
+ guint whitepoint = 0;
+ GVariantBuilder builder;
+ GVariant *retvax = NULL;
+
+ const GOptionEntry options[] = {
+ { "device", '\0', 0, G_OPTION_ARG_STRING, &device_id,
+ /* TRANSLATORS: command line option */
+ "Use this device for profiling", NULL },
+ { "sensor", '\0', 0, G_OPTION_ARG_STRING, &sensor_id,
+ /* TRANSLATORS: command line option */
+ "Use this sensor for profiling", NULL },
+ { "title", '\0', 0, G_OPTION_ARG_STRING, &title,
+ /* TRANSLATORS: command line option */
+ "Use this title for the profile", NULL },
+ { "quality", '\0', 0, G_OPTION_ARG_STRING, &quality,
+ /* TRANSLATORS: command line option */
+ "Use this quality setting: low,medium,high", NULL },
+ { "whitepoint", '\0', 0, G_OPTION_ARG_INT, &whitepoint,
+ /* TRANSLATORS: command line option */
+ "Target this specific whitepoint, or 0 for native", NULL },
+ { NULL}
+ };
+
+ gtk_init (&argc, &argv);
+
+ context = g_option_context_new ("colord-session example client");
+ g_option_context_add_main_entries (context, options, NULL);
+ g_option_context_add_group (context, gtk_get_option_group (TRUE));
+ ret = g_option_context_parse (context, &argc, &argv, &error);
+ if (!ret)
+ goto out;
+
+ priv = g_new0 (CdExamplePrivate, 1);
+ priv->loop = g_main_loop_new (NULL, FALSE);
+ priv->sample_widget = cd_sample_widget_new ();
+
+ /* set the dark theme */
+ settings = gtk_settings_get_default ();
+ g_object_set (G_OBJECT (settings),
+ "gtk-application-prefer-dark-theme", TRUE,
+ NULL);
+
+ /* load UI */
+ priv->builder = gtk_builder_new ();
+ retval = gtk_builder_add_from_file (priv->builder,
+ "./cd-example.ui",
+ &error);
+ if (retval == 0) {
+ ret = FALSE;
+ goto out;
+ }
+
+ /* get xrandr device name */
+ client = cd_client_new ();
+ ret = cd_client_connect_sync (client, NULL, &error);
+ if (!ret)
+ goto out;
+ if (device_id == NULL) {
+ ret = FALSE;
+ g_set_error (&error, 1, 0, "--device is required");
+ goto out;
+ }
+ priv->device = cd_client_find_device_sync (client,
+ device_id,
+ NULL,
+ &error);
+ if (priv->device == NULL) {
+ ret = FALSE;
+ goto out;
+ }
+ ret = cd_device_connect_sync (priv->device,
+ NULL,
+ &error);
+ if (!ret)
+ goto out;
+ name = cd_device_get_metadata_item (priv->device,
+ CD_DEVICE_METADATA_XRANDR_NAME);
+
+ /* get sensor */
+ if (sensor_id == NULL) {
+ ret = FALSE;
+ g_set_error (&error, 1, 0, "--sensor is required");
+ goto out;
+ }
+
+ /* get screen */
+ ret = cd_example_calib_setup_screen (priv, name, &error);
+ if (!ret)
+ goto out;
+
+ /* parse quality string */
+ if (g_strcmp0 (quality, "low") == 0) {
+ quality_value = 0;
+ } else if (g_strcmp0 (quality, "medium") == 0) {
+ quality_value = 1;
+ } else if (g_strcmp0 (quality, "high") == 0) {
+ quality_value = 2;
+ } else {
+ ret = FALSE;
+ g_set_error (&error, 1, 0, "--quality value not known");
+ goto out;
+ }
+
+ /* check title */
+ if (title == NULL) {
+ ret = FALSE;
+ g_set_error (&error, 1, 0, "--title is required");
+ goto out;
+ }
+
+ /* start the calibration session daemon */
+ connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ if (connection == NULL) {
+ ret = FALSE;
+ goto out;
+ }
+ priv->proxy = g_dbus_proxy_new_sync (connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.ColorHelper",
+ "/",
+ "org.freedesktop.ColorHelper.Display",
+ NULL,
+ &error);
+ if (priv->proxy == NULL) {
+ ret = FALSE;
+ goto out;
+ }
+ g_signal_connect (priv->proxy,
+ "g-properties-changed",
+ G_CALLBACK (cd_example_property_changed_cb),
+ priv);
+ g_signal_connect (priv->proxy,
+ "g-signal",
+ G_CALLBACK (cd_example_signal_cb),
+ priv);
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
+ g_variant_builder_add (&builder,
+ "{sv}",
+ "Quality",
+ g_variant_new_uint32 (quality_value));
+ g_variant_builder_add (&builder,
+ "{sv}",
+ "Whitepoint",
+ g_variant_new_uint32 (whitepoint));
+ g_variant_builder_add (&builder,
+ "{sv}",
+ "Title",
+ g_variant_new_string (title));
+ g_variant_builder_add (&builder,
+ "{sv}",
+ "DeviceKind",
+ g_variant_new_uint32 (CD_SENSOR_CAP_LCD));
+ retvax = g_dbus_proxy_call_sync (priv->proxy,
+ "Start",
+ g_variant_new ("(ssa{sv})",
+ device_id,
+ sensor_id,
+ &builder),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (retvax == NULL) {
+ ret = FALSE;
+ goto out;
+ }
+
+ /* add sample widget */
+ box = GTK_BOX (gtk_builder_get_object (priv->builder,
+ "vbox_status"));
+ priv->sample_widget = cd_sample_widget_new ();
+ gtk_widget_set_size_request (priv->sample_widget, 400, 400);
+ gtk_box_pack_start (box, priv->sample_widget, FALSE, FALSE, 0);
+ gtk_box_reorder_child (box, priv->sample_widget, 0);
+ gtk_widget_set_vexpand (priv->sample_widget, FALSE);
+ gtk_widget_set_hexpand (priv->sample_widget, FALSE);
+
+ /* connect to buttons */
+ widget = GTK_WIDGET (gtk_builder_get_object (priv->builder,
+ "button_start"));
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (cd_example_button_start_cb), priv);
+ widget = GTK_WIDGET (gtk_builder_get_object (priv->builder,
+ "button_cancel"));
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (cd_example_button_cancel_cb), priv);
+ gtk_widget_show (widget);
+
+ /* move to the right screen */
+ window = GTK_WINDOW (gtk_builder_get_object (priv->builder,
+ "dialog_calibrate"));
+ g_signal_connect (window, "realize",
+ G_CALLBACK (cd_example_window_realize_cb), priv);
+ g_signal_connect (window, "window-state-event",
+ G_CALLBACK (cd_example_window_state_cb), priv);
+ gtk_widget_set_app_paintable (GTK_WIDGET (window), TRUE);
+ gtk_window_set_keep_above (window, TRUE);
+ gtk_widget_show (GTK_WIDGET (window));
+ g_main_loop_run (priv->loop);
+
+ /* success */
+ retval = EXIT_SUCCESS;
+out:
+ if (!ret) {
+ g_print ("%s: %s\n",
+ "Failed to calibrate",
+ error->message);
+ g_error_free (error);
+ }
+ g_option_context_free (context);
+ if (priv != NULL) {
+ if (priv->loop != NULL)
+ g_main_loop_unref (priv->loop);
+ if (priv->x11_screen != NULL)
+ g_object_unref (priv->x11_screen);
+ if (priv->device != NULL)
+ g_object_unref (priv->device);
+ if (priv->proxy != NULL)
+ g_object_unref (priv->proxy);
+ g_free (priv);
+ }
+ if (client != NULL)
+ g_object_unref (client);
+ if (connection != NULL)
+ g_object_unref (connection);
+ if (retvax != NULL)
+ g_variant_unref (retvax);
+ g_free (device_id);
+ g_free (sensor_id);
+ g_free (quality);
+ g_free (title);
+ return retval;
+}
diff --git a/contrib/session-helper/cd-example.ui b/contrib/session-helper/cd-example.ui
new file mode 100644
index 0000000..4ac2343
--- /dev/null
+++ b/contrib/session-helper/cd-example.ui
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <object class="GtkDialog" id="dialog_calibrate">
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="title" translatable="yes">Display Calibration</property>
+ <property name="hide_titlebar_when_maximized">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="deletable">False</property>
+ <property name="has_resize_grip">False</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button_cancel">
+ <property name="label" translatable="yes">Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ <property name="secondary">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_start">
+ <property name="label" translatable="yes">Start</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox_status">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">15</property>
+ <child>
+ <object class="GtkImage" id="image_status">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="yalign">1</property>
+ <property name="pixel_size">200</property>
+ <property name="icon_name">address-book-new</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_status">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">Do not disturb the calibration device while in progress</property>
+ <property name="justify">center</property>
+ <property name="wrap">True</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="foreground" value="#ffffffffffff"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkProgressBar" id="progressbar_status">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_left">6</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">25</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">button_cancel</action-widget>
+ <action-widget response="0">button_start</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/contrib/session-helper/cd-main.c b/contrib/session-helper/cd-main.c
new file mode 100644
index 0000000..b1b532b
--- /dev/null
+++ b/contrib/session-helper/cd-main.c
@@ -0,0 +1,2282 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010-2012 Richard Hughes <richard@hughsie.com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+#include <locale.h>
+#include <lcms2.h>
+#include <math.h>
+
+#include "cd-debug.h"
+#include "cd-state.h"
+#include "cd-lcms-helpers.h"
+#include "cd-client-sync.h"
+#include "cd-device-sync.h"
+#include "cd-profile-sync.h"
+#include "cd-it8.h"
+#include "cd-sensor-sync.h"
+
+#define COLOR_HELPER_DBUS_SERVICE "org.freedesktop.ColorHelper"
+#define COLOR_HELPER_DBUS_PATH "/"
+#define COLOR_HELPER_DBUS_INTERFACE "org.freedesktop.ColorHelper"
+#define COLOR_HELPER_DBUS_INTERFACE_DISPLAY "org.freedesktop.ColorHelper.Display"
+
+typedef enum {
+ CD_MAIN_STATUS_IDLE,
+ CD_MAIN_STATUS_WAITING_FOR_INTERACTION,
+ CD_MAIN_STATUS_RUNNING
+} CdMainStatus;
+
+typedef enum {
+ CD_MAIN_INTERACTION_CODE_ATTACH_TO_SCREEN,
+ CD_MAIN_INTERACTION_CODE_MOVE_TO_CALIBRATION,
+ CD_MAIN_INTERACTION_CODE_MOVE_TO_SURFACE,
+ CD_MAIN_INTERACTION_CODE_NONE
+} CdMainInteractionCode;
+
+typedef enum {
+ CD_MAIN_QUALITY_LOW,
+ CD_MAIN_QUALITY_MEDIUM,
+ CD_MAIN_QUALITY_HIGH
+} CdMainQuality;
+
+typedef struct {
+ /* global */
+ CdClient *client;
+ CdMainStatus status;
+ GDBusConnection *connection;
+ GDBusNodeInfo *introspection;
+ GMainLoop *loop;
+ guint32 progress;
+ guint watcher_id;
+ CdState *state;
+
+ /* for the task */
+ CdMainInteractionCode interaction_code_last;
+ CdSensor *sensor;
+ CdDevice *device;
+ CdSensorCap device_kind;
+ GPtrArray *array;
+ cmsCIEXYZ whitepoint;
+ gdouble native_whitepoint;
+ guint target_whitepoint;
+ guint screen_brightness;
+ CdIt8 *it8_cal;
+ CdIt8 *it8_ti1;
+ CdIt8 *it8_ti3;
+ CdMainQuality quality;
+ GCancellable *cancellable;
+ gchar *title;
+ gchar *basename;
+ gchar *working_path;
+} CdMainPrivate;
+
+typedef struct {
+ CdColorRGB color;
+ CdColorRGB best_so_far;
+ gdouble error;
+} CdMainCalibrateItem;
+
+#define CD_MAIN_ERROR cd_main_error_quark()
+
+/**
+ * CdMainError:
+ */
+typedef enum {
+ CD_MAIN_ERROR_NONE,
+ CD_MAIN_ERROR_INTERNAL,
+ CD_MAIN_ERROR_FAILED_TO_FIND_DEVICE,
+ CD_MAIN_ERROR_FAILED_TO_FIND_SENSOR,
+ CD_MAIN_ERROR_FAILED_TO_FIND_TOOL,
+ CD_MAIN_ERROR_FAILED_TO_GENERATE_PROFILE,
+ CD_MAIN_ERROR_FAILED_TO_GET_WHITEPOINT,
+ CD_MAIN_ERROR_FAILED_TO_OPEN_PROFILE,
+ CD_MAIN_ERROR_FAILED_TO_SAVE_PROFILE,
+ CD_MAIN_ERROR_INVALID_VALUE,
+ CD_MAIN_ERROR_LAST
+} CdMainError;
+
+/**
+ * cd_main_error_to_string:
+ **/
+static const gchar *
+cd_main_error_to_string (CdMainError error_enum)
+{
+ if (error_enum == CD_MAIN_ERROR_INTERNAL)
+ return COLOR_HELPER_DBUS_SERVICE ".Internal";
+ if (error_enum == CD_MAIN_ERROR_FAILED_TO_FIND_DEVICE)
+ return COLOR_HELPER_DBUS_SERVICE ".FailedToFindDevice";
+ if (error_enum == CD_MAIN_ERROR_FAILED_TO_FIND_SENSOR)
+ return COLOR_HELPER_DBUS_SERVICE ".FailedToFindSensor";
+ if (error_enum == CD_MAIN_ERROR_FAILED_TO_FIND_TOOL)
+ return COLOR_HELPER_DBUS_SERVICE ".FailedToFindTool";
+ if (error_enum == CD_MAIN_ERROR_FAILED_TO_GENERATE_PROFILE)
+ return COLOR_HELPER_DBUS_SERVICE ".FailedToGenerateProfile";
+ if (error_enum == CD_MAIN_ERROR_FAILED_TO_GET_WHITEPOINT)
+ return COLOR_HELPER_DBUS_SERVICE ".FailedToGetWhitepoint";
+ if (error_enum == CD_MAIN_ERROR_FAILED_TO_OPEN_PROFILE)
+ return COLOR_HELPER_DBUS_SERVICE ".FailedToOpenProfile";
+ if (error_enum == CD_MAIN_ERROR_FAILED_TO_SAVE_PROFILE)
+ return COLOR_HELPER_DBUS_SERVICE ".FailedToSaveProfile";
+ if (error_enum == CD_MAIN_ERROR_INVALID_VALUE)
+ return COLOR_HELPER_DBUS_SERVICE ".InvalidValue";
+ return NULL;
+}
+
+/**
+ * cd_main_error_quark:
+ **/
+static GQuark
+cd_main_error_quark (void)
+{
+ guint i;
+ static GQuark quark = 0;
+ if (!quark) {
+ quark = g_quark_from_static_string ("colord");
+ for (i = 1; i < CD_MAIN_ERROR_LAST; i++) {
+ g_dbus_error_register_error (quark,
+ i,
+ cd_main_error_to_string (i));
+ }
+ }
+ return quark;
+}
+
+/**
+ * cd_main_calib_idle_delay_cb:
+ **/
+static gboolean
+cd_main_calib_idle_delay_cb (gpointer user_data)
+{
+ GMainLoop *loop = (GMainLoop *) user_data;
+ g_main_loop_quit (loop);
+ return FALSE;
+}
+
+/**
+ * cd_main_calib_idle_delay:
+ **/
+static void
+cd_main_calib_idle_delay (guint ms)
+{
+ GMainLoop *loop;
+ loop = g_main_loop_new (NULL, FALSE);
+ g_timeout_add (ms, cd_main_calib_idle_delay_cb, loop);
+ g_main_loop_run (loop);
+ g_main_loop_unref (loop);
+}
+
+/**
+ * cd_main_emit_update_sample:
+ **/
+static void
+cd_main_emit_update_sample (CdMainPrivate *priv, CdColorRGB *color)
+{
+ /* emit signal */
+ g_debug ("CdMain: Emitting UpdateSample(%f,%f,%f)",
+ color->R, color->G, color->B);
+ g_dbus_connection_emit_signal (priv->connection,
+ NULL,
+ COLOR_HELPER_DBUS_PATH,
+ COLOR_HELPER_DBUS_INTERFACE_DISPLAY,
+ "UpdateSample",
+ g_variant_new ("(ddd)",
+ color->R,
+ color->G,
+ color->B),
+ NULL);
+ cd_main_calib_idle_delay (200);
+}
+
+/**
+ * cd_main_get_sensor_image_attach:
+ **/
+static const gchar *
+cd_main_get_sensor_image_attach (CdMainPrivate *priv)
+{
+ switch (cd_sensor_get_kind (priv->sensor)) {
+ case CD_SENSOR_KIND_DUMMY:
+ case CD_SENSOR_KIND_HUEY:
+ return "huey-attach.svg";
+ case CD_SENSOR_KIND_COLOR_MUNKI:
+ return "munki-attach.svg";
+ case CD_SENSOR_KIND_SPYDER:
+ return "spyder2-attach.svg";
+ case CD_SENSOR_KIND_COLORIMTRE_HCFR:
+ return "hcfr-attach.svg";
+ case CD_SENSOR_KIND_I1_PRO:
+ return "i1-pro-attach.svg";
+ case CD_SENSOR_KIND_DTP94:
+ return "dtp94-attach.svg";
+ case CD_SENSOR_KIND_I1_DISPLAY3:
+ return "i1-display3-attach.svg";
+ case CD_SENSOR_KIND_COLORHUG:
+ return "colorhug-attach.svg";
+ case CD_SENSOR_KIND_SPYDER2:
+ return "spyder2-attach.svg";
+ case CD_SENSOR_KIND_SPYDER3:
+ return "spyder3-attach.svg";
+ default:
+ break;
+ }
+ return NULL;
+}
+
+/**
+ * cd_main_get_display_ti1:
+ **/
+static const gchar *
+cd_main_get_display_ti1 (CdMainQuality quality)
+{
+ switch (quality) {
+ case CD_MAIN_QUALITY_LOW:
+ return "display-short.ti1";
+ case CD_MAIN_QUALITY_MEDIUM:
+ return "display-normal.ti1";
+ case CD_MAIN_QUALITY_HIGH:
+ return "display-long.ti1";
+ default:
+ break;
+ }
+ return NULL;
+}
+
+/**
+ * cd_main_quality_to_text:
+ **/
+static const gchar *
+cd_main_quality_to_text (CdMainQuality quality)
+{
+ switch (quality) {
+ case CD_MAIN_QUALITY_LOW:
+ return "low";
+ case CD_MAIN_QUALITY_MEDIUM:
+ return "medium";
+ case CD_MAIN_QUALITY_HIGH:
+ return "high";
+ default:
+ break;
+ }
+ return NULL;
+}
+
+/**
+ * cd_main_get_sensor_image_calibrate:
+ **/
+static const gchar *
+cd_main_get_sensor_image_calibrate (CdMainPrivate *priv)
+{
+ CdSensorKind sensor_kind;
+
+ sensor_kind = cd_sensor_get_kind (priv->sensor);
+ if (sensor_kind == CD_SENSOR_KIND_COLOR_MUNKI)
+ return "munki-calibrate.svg";
+ return NULL;
+}
+
+/**
+ * cd_main_get_sensor_image_screen:
+ **/
+static const gchar *
+cd_main_get_sensor_image_screen (CdMainPrivate *priv)
+{
+ CdSensorKind sensor_kind;
+
+ sensor_kind = cd_sensor_get_kind (priv->sensor);
+ if (sensor_kind == CD_SENSOR_KIND_COLOR_MUNKI)
+ return "munki-screen.svg";
+ return NULL;
+}
+
+/**
+ * cd_main_emit_interaction_required:
+ **/
+static void
+cd_main_emit_interaction_required (CdMainPrivate *priv,
+ CdMainInteractionCode code)
+{
+ const gchar *image = NULL;
+ const gchar *message = NULL;
+ gchar *path;
+
+ /* save so we know what was asked for */
+ priv->interaction_code_last = code;
+
+ /* emit signal */
+ switch (code) {
+ case CD_MAIN_INTERACTION_CODE_ATTACH_TO_SCREEN:
+ image = cd_main_get_sensor_image_attach (priv);
+ message = "attach the sensor to the screen";
+ break;
+ case CD_MAIN_INTERACTION_CODE_MOVE_TO_SURFACE:
+ image = cd_main_get_sensor_image_screen (priv);
+ message = "move the sensor to the surface position";
+ break;
+ case CD_MAIN_INTERACTION_CODE_MOVE_TO_CALIBRATION:
+ image = cd_main_get_sensor_image_calibrate (priv);
+ message = "move the sensor to the calibrate position";
+ break;
+ default:
+ message = "";
+ break;
+ }
+ if (image != NULL) {
+ path = g_build_filename (DATADIR,
+ "colord",
+ "icons",
+ image,
+ NULL);
+ } else {
+ path = g_strdup ("");
+ }
+ g_debug ("CdMain: Emitting InteractionRequired(%i,%s,%s)",
+ code, message, path);
+ g_dbus_connection_emit_signal (priv->connection,
+ NULL,
+ COLOR_HELPER_DBUS_PATH,
+ COLOR_HELPER_DBUS_INTERFACE_DISPLAY,
+ "InteractionRequired",
+ g_variant_new ("(uss)",
+ code,
+ message,
+ path),
+ NULL);
+ g_free (path);
+}
+
+/**
+ * cd_main_emit_update_gamma:
+ **/
+static void
+cd_main_emit_update_gamma (CdMainPrivate *priv,
+ GPtrArray *array)
+{
+ GVariantBuilder builder;
+ guint i;
+ CdColorRGB *color;
+
+ /* emit signal */
+ g_debug ("CdMain: Emitting UpdateGamma(%i elements)",
+ array->len);
+
+ /* build the dict */
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
+ for (i = 0; i < array->len; i++) {
+ color = g_ptr_array_index (array, i);
+ g_variant_builder_add (&builder,
+ "(ddd)",
+ color->R,
+ color->G,
+ color->B);
+ }
+ g_dbus_connection_emit_signal (priv->connection,
+ NULL,
+ COLOR_HELPER_DBUS_PATH,
+ COLOR_HELPER_DBUS_INTERFACE_DISPLAY,
+ "UpdateGamma",
+ g_variant_new ("(a(ddd))",
+ &builder),
+ NULL);
+ cd_main_calib_idle_delay (200);
+}
+
+/**
+ * cd_main_emit_finished:
+ **/
+static void
+cd_main_emit_finished (CdMainPrivate *priv,
+ CdMainError exit_code,
+ const gchar *message)
+{
+ /* emit signal */
+ g_debug ("CdMain: Emitting Finished(%u,%s)",
+ exit_code, message);
+ g_dbus_connection_emit_signal (priv->connection,
+ NULL,
+ COLOR_HELPER_DBUS_PATH,
+ COLOR_HELPER_DBUS_INTERFACE_DISPLAY,
+ "Finished",
+ g_variant_new ("(us)",
+ exit_code,
+ message != NULL ? message : ""),
+ NULL);
+}
+
+/**
+ * cd_main_calib_get_sample:
+ **/
+static gboolean
+cd_main_calib_get_sample (CdMainPrivate *priv,
+ CdColorXYZ *xyz,
+ GError **error)
+{
+ CdColorXYZ *xyz_tmp;
+ gboolean ret = TRUE;
+
+ xyz_tmp = cd_sensor_get_sample_sync (priv->sensor,
+ priv->device_kind,
+ priv->cancellable,
+ error);
+ if (xyz_tmp == NULL) {
+ ret = FALSE;
+ goto out;
+ }
+ cd_color_copy_xyz (xyz_tmp, xyz);
+out:
+ if (xyz_tmp != NULL)
+ cd_color_xyz_free (xyz_tmp);
+ return ret;
+}
+
+/**
+ * cd_main_calib_get_native_whitepoint:
+ **/
+static gboolean
+cd_main_calib_get_native_whitepoint (CdMainPrivate *priv,
+ gdouble *temp,
+ GError **error)
+{
+ CdColorRGB rgb;
+ CdColorXYZ xyz;
+ cmsCIExyY chroma;
+ gboolean ret = TRUE;
+
+ rgb.R = 1.0;
+ rgb.G = 1.0;
+ rgb.B = 1.0;
+ cd_main_emit_update_sample (priv, &rgb);
+
+ ret = cd_main_calib_get_sample (priv, &xyz, error);
+ if (!ret)
+ goto out;
+
+ cmsXYZ2xyY (&chroma, (cmsCIEXYZ *) &xyz);
+ g_debug ("x:%f,y:%f,Y:%f", chroma.x, chroma.y, chroma.Y);
+ cmsTempFromWhitePoint (temp, &chroma);
+out:
+ return ret;
+}
+
+/**
+ * cd_main_calib_try_item:
+ **/
+static gboolean
+cd_main_calib_try_item (CdMainPrivate *priv,
+ CdMainCalibrateItem *item,
+ gboolean *new_best,
+ GError **error)
+{
+ gboolean ret = TRUE;
+ gdouble error_tmp;
+ CdColorXYZ xyz;
+ cmsCIELab lab;
+
+ g_debug ("try %f,%f,%f", item->color.R, item->color.G, item->color.B);
+ cd_main_emit_update_gamma (priv, priv->array);
+
+ /* get the sample using the default matrix */
+ ret = cd_main_calib_get_sample (priv, &xyz, error);
+ if (!ret)
+ goto out;
+
+ /* get error */
+ cmsXYZ2Lab (&priv->whitepoint, &lab, (const cmsCIEXYZ *) &xyz);
+ error_tmp = sqrt (lab.a * lab.a + lab.b * lab.b);
+ g_debug ("%f\t%f\t%f = %f", lab.L, lab.a, lab.b, error_tmp);
+ if (error_tmp < item->error) {
+ cd_color_copy_rgb (&item->color, &item->best_so_far);
+ item->error = error_tmp;
+ if (new_best != NULL)
+ *new_best = TRUE;
+ }
+out:
+ return ret;
+}
+
+/**
+ * cd_main_calib_process_item:
+ **/
+static gboolean
+cd_main_calib_process_item (CdMainPrivate *priv,
+ CdMainCalibrateItem *item,
+ CdState *state,
+ GError **error)
+{
+ CdState *state_local;
+ gboolean new_best = FALSE;
+ gboolean ret = TRUE;
+ gdouble good_enough_interval = 0.0f;
+ gdouble interval = 0.05;
+ gdouble tmp;
+ guint i;
+ guint number_steps = 0;
+
+ /* reset the state */
+ ret = cd_state_set_steps (state,
+ error,
+ 3, /* get baseline sample */
+ 97, /* get other samples */
+ -1);
+ if (!ret)
+ goto out;
+
+ /* copy the current color balance as the best */
+ cd_color_copy_rgb (&item->color, &item->best_so_far);
+
+ /* get a baseline error */
+ ret = cd_main_calib_try_item (priv, item, NULL, error);
+ if (!ret)
+ goto out;
+
+ /* done */
+ ret = cd_state_done (state, error);
+ if (!ret)
+ goto out;
+
+ /* use a different smallest interval for each quality */
+ if (priv->quality == CD_MAIN_QUALITY_LOW) {
+ good_enough_interval = 0.009;
+ } else if (priv->quality == CD_MAIN_QUALITY_MEDIUM) {
+ good_enough_interval = 0.006;
+ } else if (priv->quality == CD_MAIN_QUALITY_HIGH) {
+ good_enough_interval = 0.003;
+ }
+
+ /* do the progress the best we can */
+ state_local = cd_state_get_child (state);
+ for (tmp = interval; tmp > good_enough_interval; tmp /= 2)
+ number_steps++;
+ cd_state_set_number_steps (state_local, number_steps);
+ for (i = 0; i < 500; i++) {
+
+ /* check if cancelled */
+ ret = g_cancellable_set_error_if_cancelled (priv->cancellable,
+ error);
+ if (ret) {
+ ret = FALSE;
+ goto out;
+ }
+
+ /* blue */
+ cd_color_copy_rgb (&item->best_so_far, &item->color);
+ if (item->best_so_far.B > interval) {
+ item->color.B = item->best_so_far.B - interval;
+ ret = cd_main_calib_try_item (priv, item, &new_best, error);
+ if (!ret)
+ goto out;
+ if (new_best) {
+ g_debug ("New best: blue down by %f", interval);
+ new_best = FALSE;
+ continue;
+ }
+ }
+ if (item->best_so_far.B < 1.0 - interval) {
+ item->color.B = item->best_so_far.B + interval;
+ ret = cd_main_calib_try_item (priv, item, &new_best, error);
+ if (!ret)
+ goto out;
+ if (new_best) {
+ g_debug ("New best: blue up by %f", interval);
+ new_best = FALSE;
+ continue;
+ }
+ }
+
+ /* red */
+ cd_color_copy_rgb (&item->best_so_far, &item->color);
+ if (item->best_so_far.R > interval) {
+ item->color.R = item->best_so_far.R - interval;
+ ret = cd_main_calib_try_item (priv, item, &new_best, error);
+ if (!ret)
+ goto out;
+ if (new_best) {
+ g_debug ("New best: red down by %f", interval);
+ new_best = FALSE;
+ continue;
+ }
+ }
+ if (item->best_so_far.R < 1.0 - interval) {
+ item->color.R = item->best_so_far.R + interval;
+ ret = cd_main_calib_try_item (priv, item, &new_best, error);
+ if (!ret)
+ goto out;
+ if (new_best) {
+ g_debug ("New best: red up by %f", interval);
+ new_best = FALSE;
+ continue;
+ }
+ }
+
+ /* green */
+ cd_color_copy_rgb (&item->best_so_far, &item->color);
+ if (item->best_so_far.G > interval) {
+ item->color.G = item->best_so_far.G - interval;
+ ret = cd_main_calib_try_item (priv, item, &new_best, error);
+ if (!ret)
+ goto out;
+ if (new_best) {
+ g_debug ("New best: green down by %f", interval);
+ new_best = FALSE;
+ continue;
+ }
+ }
+ if (item->best_so_far.G < 1.0 - interval) {
+ item->color.G = item->best_so_far.G + interval;
+ ret = cd_main_calib_try_item (priv, item, &new_best, error);
+ if (!ret)
+ goto out;
+ if (new_best) {
+ g_debug ("New best: green up by %f", interval);
+ new_best = FALSE;
+ continue;
+ }
+ }
+
+ /* done */
+ ret = cd_state_done (state_local, error);
+ if (!ret)
+ goto out;
+
+ /* done */
+ interval /= 2;
+ if (interval < good_enough_interval) {
+ g_debug ("no improvement, best RGB was: %f,%f,%f",
+ item->best_so_far.R,
+ item->best_so_far.G,
+ item->best_so_far.B);
+ break;
+ }
+ }
+
+ /* save this */
+ cd_color_copy_rgb (&item->best_so_far,
+ &item->color);
+
+ /* done */
+ ret = cd_state_done (state, error);
+ if (!ret)
+ goto out;
+out:
+ return ret;
+}
+
+/**
+ * cd_main_calib_interpolate_up:
+ *
+ * Interpolate from the current number of points to a new size
+ **/
+static gboolean
+cd_main_calib_interpolate_up (CdMainPrivate *priv,
+ guint new_size,
+ GError **error)
+{
+ CdMainCalibrateItem *p1;
+ CdMainCalibrateItem *p2;
+ CdMainCalibrateItem *result;
+ gboolean ret = TRUE;
+ gdouble mix;
+ guint i;
+ GPtrArray *old_array;
+
+ /* make a deep copy */
+ old_array = g_ptr_array_new_with_free_func (g_free);
+ for (i = 0; i < priv->array->len; i++) {
+ p1 = g_ptr_array_index (priv->array, i);
+ result = g_new (CdMainCalibrateItem, 1);
+ result->error = p1->error;
+ cd_color_copy_rgb (&p1->color, &result->color);
+ g_ptr_array_add (old_array, result);
+ }
+
+ /* interpolate the new array */
+ g_ptr_array_set_size (priv->array, 0);
+ for (i = 0; i < new_size; i++) {
+ mix = (gdouble) (old_array->len - 1) /
+ (gdouble) (new_size - 1) *
+ (gdouble) i;
+ p1 = g_ptr_array_index (old_array, (guint) floor (mix));
+ p2 = g_ptr_array_index (old_array, (guint) ceil (mix));
+ result = g_new (CdMainCalibrateItem, 1);
+ result->error = G_MAXDOUBLE;
+ cd_color_set_rgb (&result->color, 1.0, 1.0, 1.0);
+ cd_color_rgb_interpolate (&p1->color,
+ &p2->color,
+ mix - (gint) mix,
+ &result->color);
+ g_ptr_array_add (priv->array, result);
+ }
+//out:
+ g_ptr_array_unref (old_array);
+ return ret;
+}
+
+/**
+ * cd_main_calib_process:
+ **/
+static gboolean
+cd_main_calib_process (CdMainPrivate *priv,
+ CdState *state,
+ GError **error)
+{
+ CdColorRGB rgb;
+ CdMainCalibrateItem *item;
+ CdState *state_local;
+ CdState *state_loop;
+ cmsCIExyY whitepoint_tmp;
+ gboolean ret;
+ gdouble temp;
+ guint i;
+ guint precision_steps = 0;
+
+ /* reset the state */
+ ret = cd_state_set_steps (state,
+ error,
+ 1, /* get native whitepoint */
+ 3, /* normalize white */
+ 94, /* refine other points */
+ 1, /* get new whitepoint */
+ 1, /* write calibrate point */
+ -1);
+ if (!ret)
+ goto out;
+
+ /* clear gamma ramp to linear */
+ priv->array = g_ptr_array_new_with_free_func (g_free);
+ item = g_new0 (CdMainCalibrateItem, 1);
+ item->error = G_MAXDOUBLE;
+ cd_color_set_rgb (&item->color, 0.0, 0.0, 0.0);
+ g_ptr_array_add (priv->array, item);
+ item = g_new0 (CdMainCalibrateItem, 1);
+ item->error = G_MAXDOUBLE;
+ cd_color_set_rgb (&item->color, 1.0, 1.0, 1.0);
+ g_ptr_array_add (priv->array, item);
+ cd_main_emit_update_gamma (priv, priv->array);
+
+ /* get whitepoint */
+ ret = cd_main_calib_get_native_whitepoint (priv, &priv->native_whitepoint, error);
+ if (!ret)
+ goto out;
+ if (priv->native_whitepoint < 1000 ||
+ priv->native_whitepoint > 100000) {
+ ret = FALSE;
+ g_set_error_literal (error,
+ CD_MAIN_ERROR,
+ CD_MAIN_ERROR_FAILED_TO_GET_WHITEPOINT,
+ "failed to get native temperature");
+ goto out;
+ }
+ g_debug ("native temperature %f", priv->native_whitepoint);
+
+ /* get the target whitepoint XYZ for the Lab check */
+ if (priv->target_whitepoint > 0) {
+ cmsWhitePointFromTemp (&whitepoint_tmp,
+ (gdouble) priv->target_whitepoint);
+ } else {
+ cmsWhitePointFromTemp (&whitepoint_tmp,
+ priv->native_whitepoint);
+ }
+ cmsxyY2XYZ (&priv->whitepoint, &whitepoint_tmp);
+
+ /* done */
+ ret = cd_state_done (state, error);
+ if (!ret)
+ goto out;
+
+ /* should we seed the first value with a good approximation */
+ if (priv->target_whitepoint > 0) {
+ CdColorRGB tmp;
+ cd_color_get_blackbody_rgb (6500 - (priv->native_whitepoint - priv->target_whitepoint), &tmp);
+ g_debug ("Seeding with %f,%f,%f",
+ tmp.R, tmp.G, tmp.B);
+ cd_color_copy_rgb (&tmp, &item->color);
+ }
+
+ /* process the last item in the array (255,255,255) */
+ item = g_ptr_array_index (priv->array, 1);
+ state_local = cd_state_get_child (state);
+ ret = cd_main_calib_process_item (priv,
+ item,
+ state_local,
+ error);
+ if (!ret)
+ goto out;
+
+ /* ensure white is normalised to 1 */
+ temp = 1.0f / (gdouble) MAX (MAX (item->color.R, item->color.G), item->color.B);
+ item->color.R *= temp;
+ item->color.G *= temp;
+ item->color.B *= temp;
+
+ /* done */
+ ret = cd_state_done (state, error);
+ if (!ret)
+ goto out;
+
+ /* expand out the array into more points (interpolating) */
+ if (priv->quality == CD_MAIN_QUALITY_LOW) {
+ precision_steps = 5;
+ } else if (priv->quality == CD_MAIN_QUALITY_MEDIUM) {
+ precision_steps = 11;
+ } else if (priv->quality == CD_MAIN_QUALITY_HIGH) {
+ precision_steps = 21;
+ }
+ ret = cd_main_calib_interpolate_up (priv, precision_steps, error);
+ if (!ret)
+ goto out;
+
+ /* refine the other points */
+ state_local = cd_state_get_child (state);
+ cd_state_set_number_steps (state_local, priv->array->len - 1);
+ for (i = priv->array->len - 2; i > 0 ; i--) {
+
+ /* set new sample patch */
+ rgb.R = 1.0 / (gdouble) (priv->array->len - 1) * (gdouble) i;
+ rgb.G = 1.0 / (gdouble) (priv->array->len - 1) * (gdouble) i;
+ rgb.B = 1.0 / (gdouble) (priv->array->len - 1) * (gdouble) i;
+ cd_main_emit_update_sample (priv, &rgb);
+
+ /* process this section */
+ item = g_ptr_array_index (priv->array, i);
+ state_loop = cd_state_get_child (state_local);
+ ret = cd_main_calib_process_item (priv, item, state_loop, error);
+ if (!ret)
+ goto out;
+
+ /* done */
+ ret = cd_state_done (state_local, error);
+ if (!ret)
+ goto out;
+ }
+
+ /* done */
+ ret = cd_state_done (state, error);
+ if (!ret)
+ goto out;
+
+ /* set this */
+ cd_main_emit_update_gamma (priv, priv->array);
+
+ /* get new whitepoint */
+ ret = cd_main_calib_get_native_whitepoint (priv, &temp, error);
+ if (!ret)
+ goto out;
+ g_debug ("new native temperature %f", temp);
+
+ /* done */
+ ret = cd_state_done (state, error);
+ if (!ret)
+ goto out;
+
+ /* save the results */
+ priv->it8_cal = cd_it8_new_with_kind (CD_IT8_KIND_CAL);
+ cd_it8_set_originator (priv->it8_cal, "colord-session");
+ cd_it8_set_instrument (priv->it8_cal, cd_sensor_kind_to_string (cd_sensor_get_kind (priv->sensor)));
+ ret = cd_main_calib_interpolate_up (priv, 256, error);
+ if (!ret)
+ goto out;
+ for (i = 0; i < priv->array->len; i++) {
+ item = g_ptr_array_index (priv->array, i);
+ cd_it8_add_data (priv->it8_cal, &item->color, NULL);
+ }
+
+ /* done */
+ ret = cd_state_done (state, error);
+ if (!ret)
+ goto out;
+out:
+ return ret;
+}
+
+/**
+ * cd_main_finished_quit_cb:
+ **/
+static gboolean
+cd_main_finished_quit_cb (gpointer user_data)
+{
+ CdMainPrivate *priv = (CdMainPrivate *) user_data;
+ g_main_loop_quit (priv->loop);
+ return FALSE;
+}
+
+/**
+ * cd_main_load_samples:
+ **/
+static gboolean
+cd_main_load_samples (CdMainPrivate *priv, GError **error)
+{
+ const gchar *filename;
+ gboolean ret;
+ gchar *path;
+ GFile *file;
+
+ filename = cd_main_get_display_ti1 (priv->quality);
+ path = g_build_filename (DATADIR,
+ "colord",
+ "ti1",
+ filename,
+ NULL);
+ g_debug ("opening source file %s", path);
+ file = g_file_new_for_path (path);
+ priv->it8_ti1 = cd_it8_new ();
+ ret = cd_it8_load_from_file (priv->it8_ti1, file, error);
+ if (!ret)
+ goto out;
+out:
+ g_free (path);
+ g_object_unref (file);
+ return ret;
+}
+
+/**
+ * cd_main_write_colprof_files:
+ **/
+static gboolean
+cd_main_write_colprof_files (CdMainPrivate *priv, GError **error)
+{
+ gboolean ret = TRUE;
+ gchar *filename_ti3 = NULL;
+ gchar *data_cal = NULL;
+ gchar *data_ti3 = NULL;
+ gchar *data = NULL;
+ gchar *path_ti3 = NULL;
+
+ /* build temp path */
+ priv->working_path = g_dir_make_tmp ("colord-session-XXXXXX", error);
+ if (priv->working_path == NULL) {
+ ret = FALSE;
+ goto out;
+ }
+
+ /* save .ti3 with ti1 and cal data appended together */
+ ret = cd_it8_save_to_data (priv->it8_ti3,
+ &data_ti3,
+ NULL,
+ error);
+ if (!ret)
+ goto out;
+ ret = cd_it8_save_to_data (priv->it8_cal,
+ &data_cal,
+ NULL,
+ error);
+ if (!ret)
+ goto out;
+ data = g_strdup_printf ("%s\n%s", data_ti3, data_cal);
+ filename_ti3 = g_strdup_printf ("%s.ti3", priv->basename);
+ path_ti3 = g_build_filename (priv->working_path,
+ filename_ti3,
+ NULL);
+ g_debug ("saving %s", path_ti3);
+ ret = g_file_set_contents (path_ti3, data, -1, error);
+ if (!ret)
+ goto out;
+out:
+ g_free (data);
+ g_free (data_cal);
+ g_free (data_ti3);
+ g_free (filename_ti3);
+ g_free (path_ti3);
+ return ret;
+}
+
+/**
+ * cd_main_get_colprof_quality_arg:
+ **/
+static const gchar *
+cd_main_get_colprof_quality_arg (CdMainQuality quality)
+{
+ if (quality == CD_MAIN_QUALITY_LOW)
+ return "-ql";
+ if (quality == CD_MAIN_QUALITY_MEDIUM)
+ return "-qm";
+ if (quality == CD_MAIN_QUALITY_HIGH)
+ return "-qh";
+ return NULL;
+}
+
+#define CD_PROFILE_DEFAULT_COPYRIGHT_STRING "This profile is free of known copyright restrictions."
+
+/**
+ * cd_main_find_argyll_tool:
+ **/
+static gchar *
+cd_main_find_argyll_tool (const gchar *command,
+ GError **error)
+{
+ gboolean ret;
+ gchar *filename;
+
+ /* try the original argyllcms filename installed in /usr/local/bin */
+ filename = g_strdup_printf ("/usr/local/bin/%s", command);
+ ret = g_file_test (filename, G_FILE_TEST_EXISTS);
+ if (ret)
+ goto out;
+
+ /* try the debian filename installed in /usr/bin */
+ g_free (filename);
+ filename = g_strdup_printf ("/usr/bin/argyll-%s", command);
+ ret = g_file_test (filename, G_FILE_TEST_EXISTS);
+ if (ret)
+ goto out;
+
+ /* try the original argyllcms filename installed in /usr/bin */
+ g_free (filename);
+ filename = g_strdup_printf ("/usr/bin/%s", command);
+ ret = g_file_test (filename, G_FILE_TEST_EXISTS);
+ if (ret)
+ goto out;
+
+ /* eek */
+ g_free (filename);
+ filename = NULL;
+ g_set_error (error,
+ CD_MAIN_ERROR,
+ CD_MAIN_ERROR_FAILED_TO_FIND_TOOL,
+ "failed to get filename for %s", command);
+out:
+ return filename;
+}
+
+/**
+ * cd_main_import_profile:
+ **/
+static gboolean
+cd_main_import_profile (CdMainPrivate *priv, GError **error)
+{
+ CdProfile *profile;
+ gboolean ret = TRUE;
+ gchar *filename;
+ gchar *path;
+ GFile *file;
+
+ filename = g_strdup_printf ("%s.icc", priv->basename);
+ path = g_build_filename (priv->working_path,
+ filename,
+ NULL);
+ g_debug ("trying to import %s", path);
+ file = g_file_new_for_path (path);
+ profile = cd_client_import_profile_sync (priv->client,
+ file,
+ priv->cancellable,
+ error);
+ if (profile == NULL) {
+ ret = FALSE;
+ goto out;
+ }
+ g_debug ("imported %s", cd_profile_get_object_path (profile));
+
+ /* add profile to device and set default */
+ ret = cd_profile_connect_sync (profile,
+ priv->cancellable,
+ error);
+ if (!ret)
+ goto out;
+ ret = cd_device_add_profile_sync (priv->device,
+ CD_DEVICE_RELATION_HARD,
+ profile,
+ priv->cancellable,
+ error);
+ if (!ret)
+ goto out;
+ ret = cd_device_make_profile_default_sync (priv->device,
+ profile,
+ priv->cancellable,
+ error);
+ if (!ret)
+ goto out;
+ g_debug ("set %s default on %s",
+ cd_profile_get_id (profile),
+ cd_device_get_id (priv->device));
+out:
+ g_free (filename);
+ g_free (path);
+ g_object_unref (file);
+ if (profile != NULL)
+ g_object_unref (profile);
+ return ret;
+}
+
+/**
+ * cd_main_set_profile_metadata:
+ **/
+static gboolean
+cd_main_set_profile_metadata (CdMainPrivate *priv, GError **error)
+{
+ cmsHANDLE dict_md = NULL;
+ cmsHPROFILE lcms_profile = NULL;
+ gboolean ret;
+ gchar *brightness_str = NULL;
+ gchar *data = NULL;
+ gchar *profile_fn = NULL;
+ gchar *profile_path = NULL;
+ gsize len;
+
+ /* get profile */
+ profile_fn = g_strdup_printf ("%s.icc", priv->basename);
+ profile_path = g_build_filename (priv->working_path,
+ profile_fn,
+ NULL);
+
+ /* open profile */
+ ret = g_file_get_contents (profile_path, &data, &len, error);
+ if (!ret)
+ goto out;
+ lcms_profile = cmsOpenProfileFromMem (data, len);
+ if (lcms_profile == NULL) {
+ ret = FALSE;
+ g_set_error (error,
+ CD_MAIN_ERROR,
+ CD_MAIN_ERROR_FAILED_TO_OPEN_PROFILE,
+ "failed to open profile %s",
+ profile_path);
+ goto out;
+ }
+
+ /* add DICT data */
+ dict_md = cmsDictAlloc (NULL);
+ _cmsDictAddEntryAscii (dict_md,
+ CD_PROFILE_METADATA_CMF_PRODUCT,
+ "colord");
+ _cmsDictAddEntryAscii (dict_md,
+ CD_PROFILE_METADATA_CMF_BINARY,
+ "colord-session");
+ _cmsDictAddEntryAscii (dict_md,
+ CD_PROFILE_METADATA_CMF_VERSION,
+ PACKAGE_VERSION);
+ _cmsDictAddEntryAscii (dict_md,
+ CD_PROFILE_METADATA_DATA_SOURCE,
+ CD_PROFILE_METADATA_DATA_SOURCE_CALIB);
+ _cmsDictAddEntryAscii (dict_md,
+ CD_PROFILE_METADATA_LICENSE,
+ "CC0");
+ _cmsDictAddEntryAscii (dict_md,
+ CD_PROFILE_METADATA_MAPPING_DEVICE_ID,
+ cd_device_get_id (priv->device));
+ _cmsDictAddEntryAscii (dict_md,
+ CD_PROFILE_METADATA_MEASUREMENT_DEVICE,
+ cd_sensor_kind_to_string (cd_sensor_get_kind (priv->sensor)));
+ if (priv->screen_brightness > 0) {
+ brightness_str = g_strdup_printf ("%u", priv->screen_brightness);
+ _cmsDictAddEntryAscii (dict_md,
+ CD_PROFILE_METADATA_SCREEN_BRIGHTNESS,
+ brightness_str);
+ g_free (brightness_str);
+ }
+ ret = cmsWriteTag (lcms_profile, cmsSigMetaTag, dict_md);
+ if (!ret) {
+ g_set_error_literal (error,
+ CD_MAIN_ERROR,
+ CD_MAIN_ERROR_FAILED_TO_SAVE_PROFILE,
+ "cannot initialize dict tag");
+ goto out;
+ }
+
+ /* write profile id */
+ ret = cmsMD5computeID (lcms_profile);
+ if (!ret) {
+ g_set_error (error,
+ CD_MAIN_ERROR,
+ CD_MAIN_ERROR_INTERNAL,
+ "failed to compute profile id for %s",
+ profile_path);
+ goto out;
+ }
+
+ /* save file */
+ ret = cmsSaveProfileToFile (lcms_profile, profile_path);
+ if (!ret) {
+ g_set_error (error,
+ CD_MAIN_ERROR,
+ CD_MAIN_ERROR_FAILED_TO_SAVE_PROFILE,
+ "failed to save profile to %s",
+ profile_path);
+ goto out;
+ }
+out:
+ g_free (profile_fn);
+ g_free (profile_path);
+ g_free (data);
+ if (dict_md != NULL)
+ cmsDictFree (dict_md);
+ if (lcms_profile != NULL)
+ cmsCloseProfile (lcms_profile);
+ return ret;
+}
+
+/**
+ * cd_main_generate_profile:
+ **/
+static gboolean
+cd_main_generate_profile (CdMainPrivate *priv, GError **error)
+{
+ gboolean ret;
+ gchar *command;
+ gchar *stderr = NULL;
+ gint exit_status = 0;
+ gchar *cmd_debug = NULL;
+ GPtrArray *array = NULL;
+
+ /* get correct name of the command */
+ command = cd_main_find_argyll_tool ("colprof", error);
+ if (command == NULL) {
+ ret = FALSE;
+ goto out;
+ }
+
+ /* argument array */
+ array = g_ptr_array_new_with_free_func (g_free);
+
+ /* setup the command */
+ g_ptr_array_add (array, g_strdup (command));
+ g_ptr_array_add (array, g_strdup ("-v"));
+// g_ptr_array_add (array, g_strdup_printf ("-A%s", cd_device_get_vendor (priv->device)));
+ g_ptr_array_add (array, g_strdup_printf ("-M%s", cd_device_get_model (priv->device)));
+ g_ptr_array_add (array, g_strdup_printf ("-D%s", priv->title));
+ g_ptr_array_add (array, g_strdup_printf ("-C%s", CD_PROFILE_DEFAULT_COPYRIGHT_STRING));
+ g_ptr_array_add (array, g_strdup (cd_main_get_colprof_quality_arg (priv->quality)));
+ g_ptr_array_add (array, g_strdup ("-aG"));
+ g_ptr_array_add (array, g_strdup (priv->basename));
+ g_ptr_array_add (array, NULL);
+
+ /* run the command */
+ cmd_debug = g_strjoinv (" ", (gchar **) array->pdata);
+ g_debug ("running '%s'", cmd_debug);
+ ret = g_spawn_sync (priv->working_path,
+ (gchar **) array->pdata,
+ NULL,
+ 0,
+ NULL, NULL,
+ NULL,
+ &stderr,
+ &exit_status,
+ error);
+ if (!ret)
+ goto out;
+ if (exit_status != 0) {
+ ret = FALSE;
+ g_set_error (error,
+ CD_MAIN_ERROR,
+ CD_MAIN_ERROR_FAILED_TO_GENERATE_PROFILE,
+ "colprof failed: %s", stderr);
+ goto out;
+ }
+out:
+ if (array != NULL)
+ g_ptr_array_unref (array);
+ g_free (command);
+ g_free (stderr);
+ g_free (cmd_debug);
+ return ret;
+}
+
+/**
+ * cd_main_display_get_samples:
+ **/
+static gboolean
+cd_main_display_get_samples (CdMainPrivate *priv,
+ CdState *state,
+ GError **error)
+{
+ CdColorRGB rgb;
+ CdColorXYZ xyz;
+ gboolean ret = TRUE;
+ guint i;
+ guint size;
+
+ size = cd_it8_get_data_size (priv->it8_ti1);
+ cd_state_set_number_steps (state, size);
+ for (i = 0; i < size; i++) {
+ cd_it8_get_data_item (priv->it8_ti1,
+ i,
+ &rgb,
+ NULL);
+ cd_main_emit_update_sample (priv, &rgb);
+ ret = cd_main_calib_get_sample (priv, &xyz, error);
+ if (!ret)
+ goto out;
+ cd_it8_add_data (priv->it8_ti3,
+ &rgb,
+ &xyz);
+
+ /* done */
+ ret = cd_state_done (state, error);
+ if (!ret)
+ goto out;
+ }
+out:
+ return ret;
+}
+
+/**
+ * cd_main_display_characterize:
+ **/
+static gboolean
+cd_main_display_characterize (CdMainPrivate *priv,
+ CdState *state,
+ GError **error)
+{
+ CdState *state_local;
+ gboolean ret;
+
+ /* reset the state */
+ ret = cd_state_set_steps (state,
+ error,
+ 1, /* load samples */
+ 96, /* measure samples */
+ 1, /* run colprof */
+ 1, /* set metadata */
+ 1, /* import profile */
+ -1);
+ if (!ret)
+ goto out;
+
+ /* load the ti1 file */
+ ret = cd_main_load_samples (priv, error);
+ if (!ret)
+ goto out;
+
+ /* done */
+ ret = cd_state_done (state, error);
+ if (!ret)
+ goto out;
+
+ /* create the ti3 file */
+ priv->it8_ti3 = cd_it8_new_with_kind (CD_IT8_KIND_TI3);
+ cd_it8_set_normalized (priv->it8_ti3, TRUE);
+ cd_it8_set_originator (priv->it8_ti3, "colord-session");
+ cd_it8_set_title (priv->it8_ti3, priv->title);
+ cd_it8_set_spectral (priv->it8_ti3, FALSE);
+ cd_it8_set_instrument (priv->it8_ti3, cd_sensor_get_model (priv->sensor));
+
+ /* measure each sample */
+ state_local = cd_state_get_child (state);
+ ret = cd_main_display_get_samples (priv, state_local, error);
+ if (!ret)
+ goto out;
+
+ /* done */
+ ret = cd_state_done (state, error);
+ if (!ret)
+ goto out;
+
+ /* write out files */
+ ret = cd_main_write_colprof_files (priv, error);
+ if (!ret)
+ goto out;
+
+ /* run colprof */
+ ret = cd_main_generate_profile (priv, error);
+ if (!ret)
+ goto out;
+
+ /* done */
+ ret = cd_state_done (state, error);
+ if (!ret)
+ goto out;
+
+ /* set metadata on the profile */
+ ret = cd_main_set_profile_metadata (priv, error);
+ if (!ret)
+ goto out;
+
+ /* done */
+ ret = cd_state_done (state, error);
+ if (!ret)
+ goto out;
+
+ /* import profile */
+ ret = cd_main_import_profile (priv, error);
+ if (!ret)
+ goto out;
+
+ /* done */
+ ret = cd_state_done (state, error);
+ if (!ret)
+ goto out;
+out:
+ return ret;
+}
+
+/**
+ * cd_main_remove_temp_file:
+ **/
+static gboolean
+cd_main_remove_temp_file (const gchar *filename,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret;
+ GFile *file;
+
+ g_debug ("removing %s", filename);
+ file = g_file_new_for_path (filename);
+ ret = g_file_delete (file, cancellable, error);
+ if (!ret)
+ goto out;
+out:
+ g_object_unref (file);
+ return ret;
+}
+
+/**
+ * cd_main_remove_temp_files:
+ **/
+static gboolean
+cd_main_remove_temp_files (CdMainPrivate *priv, GError **error)
+{
+ const gchar *filename;
+ gboolean ret;
+ gchar *src;
+ GDir *dir;
+
+ /* try to open */
+ dir = g_dir_open (priv->working_path, 0, error);
+ if (dir == NULL) {
+ ret = FALSE;
+ goto out;
+ }
+
+ /* find each */
+ while ((filename = g_dir_read_name (dir))) {
+ src = g_build_filename (priv->working_path,
+ filename,
+ NULL);
+ ret = cd_main_remove_temp_file (src,
+ priv->cancellable,
+ error);
+ g_free (src);
+ if (!ret)
+ goto out;
+ }
+
+ /* remove directory */
+ ret = cd_main_remove_temp_file (priv->working_path,
+ priv->cancellable,
+ error);
+ if (!ret)
+ goto out;
+out:
+ if (dir != NULL)
+ g_dir_close (dir);
+ return ret;
+}
+
+/**
+ * cd_main_start_calibration:
+ **/
+static gboolean
+cd_main_start_calibration (CdMainPrivate *priv,
+ CdState *state,
+ GError **error)
+{
+ CdState *state_local;
+ gboolean ret;
+ GError *error_local = NULL;
+
+ /* reset the state */
+ ret = cd_state_set_steps (state,
+ error,
+ 74, /* calibration */
+ 25, /* characterization */
+ 1, /* remove temp files */
+ -1);
+ if (!ret)
+ goto out;
+
+ /* do the calibration */
+ state_local = cd_state_get_child (state);
+ ret = cd_main_calib_process (priv, state_local, &error_local);
+ if (!ret) {
+ if (g_error_matches (error_local,
+ CD_SENSOR_ERROR,
+ CD_SENSOR_ERROR_REQUIRED_POSITION_CALIBRATE)) {
+ priv->status = CD_MAIN_STATUS_WAITING_FOR_INTERACTION;
+ cd_main_emit_interaction_required (priv,
+ CD_MAIN_INTERACTION_CODE_MOVE_TO_CALIBRATION);
+ g_error_free (error_local);
+ } else if (g_error_matches (error_local,
+ CD_SENSOR_ERROR,
+ CD_SENSOR_ERROR_REQUIRED_POSITION_SURFACE)) {
+ priv->status = CD_MAIN_STATUS_WAITING_FOR_INTERACTION;
+ cd_main_emit_interaction_required (priv,
+ CD_MAIN_INTERACTION_CODE_MOVE_TO_SURFACE);
+ g_error_free (error_local);
+ } else {
+ g_propagate_error (error, error_local);
+ goto out;
+ }
+ goto out;
+ }
+
+ /* done */
+ ret = cd_state_done (state, error);
+ if (!ret)
+ goto out;
+
+ /* do the characterization */
+ state_local = cd_state_get_child (state);
+ ret = cd_main_display_characterize (priv, state_local, error);
+ if (!ret)
+ goto out;
+
+ /* done */
+ ret = cd_state_done (state, error);
+ if (!ret)
+ goto out;
+
+ /* remove temp files */
+ ret = cd_main_remove_temp_files (priv, error);
+ if (!ret)
+ goto out;
+
+ /* done */
+ ret = cd_state_done (state, error);
+ if (!ret)
+ goto out;
+out:
+ return ret;
+}
+
+/**
+ * cd_main_start_calibration_cb:
+ **/
+static gboolean
+cd_main_start_calibration_cb (gpointer user_data)
+{
+ CdMainPrivate *priv = (CdMainPrivate *) user_data;
+ gboolean ret;
+ GError *error = NULL;
+
+ /* reset the state */
+ cd_state_reset (priv->state);
+ ret = cd_main_start_calibration (priv,
+ priv->state,
+ &error);
+ if (!ret) {
+ /* use the error code if it's our error domain */
+ if (error->domain == CD_MAIN_ERROR) {
+ cd_main_emit_finished (priv,
+ error->code,
+ error->message);
+ } else {
+ cd_main_emit_finished (priv,
+ CD_MAIN_ERROR_INTERNAL,
+ error->message);
+ }
+ g_timeout_add (200, cd_main_finished_quit_cb, priv);
+ g_error_free (error);
+ goto out;
+ }
+
+ /* success */
+ cd_main_emit_finished (priv, CD_MAIN_ERROR_NONE, NULL);
+ g_timeout_add (200, cd_main_finished_quit_cb, priv);
+out:
+ return FALSE;
+}
+
+/**
+ * cd_main_status_to_text:
+ **/
+static const gchar *
+cd_main_status_to_text (CdMainStatus status)
+{
+ if (status == CD_MAIN_STATUS_IDLE)
+ return "idle";
+ if (status == CD_MAIN_STATUS_WAITING_FOR_INTERACTION)
+ return "waiting-for-interaction";
+ if (status == CD_MAIN_STATUS_RUNNING)
+ return "running";
+ return NULL;
+}
+
+/**
+ * cd_main_sender_vanished_cb:
+ **/
+static void
+cd_main_sender_vanished_cb (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ CdMainPrivate *priv = (CdMainPrivate *) user_data;
+
+ /* FIXME: make configurable? */
+ g_debug ("Quitting daemon as sender has quit");
+ g_cancellable_cancel (priv->cancellable);
+ g_main_loop_quit (priv->loop);
+}
+
+/**
+ * cd_main_find_device:
+ **/
+static CdDevice *
+cd_main_find_device (CdMainPrivate *priv,
+ const gchar *device_id,
+ GError **error)
+{
+ CdDevice *device = NULL;
+ CdDevice *device_tmp;
+ gboolean ret;
+ GError *error_local = NULL;
+
+ device_tmp = cd_client_find_device_sync (priv->client,
+ device_id,
+ NULL,
+ &error_local);
+ if (device_tmp == NULL) {
+ g_set_error (error,
+ CD_MAIN_ERROR,
+ CD_MAIN_ERROR_FAILED_TO_FIND_DEVICE,
+ "%s", error_local->message);
+ g_error_free (error_local);
+ goto out;
+ }
+ ret = cd_device_connect_sync (device_tmp,
+ NULL,
+ &error_local);
+ if (!ret) {
+ g_set_error (error,
+ CD_MAIN_ERROR,
+ CD_MAIN_ERROR_FAILED_TO_FIND_DEVICE,
+ "%s", error_local->message);
+ g_error_free (error_local);
+ goto out;
+ }
+
+ /* mark device to be profiled in colord */
+ ret = cd_device_profiling_inhibit_sync (device_tmp,
+ NULL,
+ &error_local);
+ if (!ret) {
+ g_set_error (error,
+ CD_MAIN_ERROR,
+ CD_MAIN_ERROR_INTERNAL,
+ "%s", error_local->message);
+ g_error_free (error_local);
+ goto out;
+ }
+
+ /* success */
+ device = g_object_ref (device_tmp);
+out:
+ if (device_tmp != NULL)
+ g_object_unref (device_tmp);
+ return device;
+}
+
+/**
+ * cd_main_find_sensor:
+ **/
+static CdSensor *
+cd_main_find_sensor (CdMainPrivate *priv,
+ const gchar *sensor_id,
+ GError **error)
+{
+ CdSensor *sensor_tmp;
+ CdSensor *sensor = NULL;
+ gboolean ret;
+ GError *error_local = NULL;
+
+ sensor_tmp = cd_client_find_sensor_sync (priv->client,
+ sensor_id,
+ NULL,
+ &error_local);
+ if (sensor_tmp == NULL) {
+ g_set_error (error,
+ CD_MAIN_ERROR,
+ CD_MAIN_ERROR_FAILED_TO_FIND_SENSOR,
+ "%s", error_local->message);
+ g_error_free (error_local);
+ goto out;
+ }
+ ret = cd_sensor_connect_sync (sensor_tmp,
+ NULL,
+ &error_local);
+ if (!ret) {
+ g_set_error (error,
+ CD_MAIN_ERROR,
+ CD_MAIN_ERROR_FAILED_TO_FIND_SENSOR,
+ "%s", error_local->message);
+ g_error_free (error_local);
+ goto out;
+ }
+
+ /* lock the sensor */
+ ret = cd_sensor_lock_sync (sensor_tmp,
+ NULL,
+ &error_local);
+ if (!ret) {
+ g_set_error (error,
+ CD_MAIN_ERROR,
+ CD_MAIN_ERROR_FAILED_TO_FIND_SENSOR,
+ "%s", error_local->message);
+ g_error_free (error_local);
+ goto out;
+ }
+
+ /* success */
+ sensor = g_object_ref (sensor_tmp);
+out:
+ if (sensor_tmp != NULL)
+ g_object_unref (sensor_tmp);
+ return sensor;
+}
+
+/**
+ * cd_main_set_basename:
+ **/
+static void
+cd_main_set_basename (CdMainPrivate *priv)
+{
+ const gchar *tmp;
+ gchar *date_str = NULL;
+ GDateTime *datetime;
+ GString *str;
+
+ str = g_string_new ("");
+
+ /* add vendor */
+ tmp = cd_device_get_vendor (priv->device);
+ if (tmp != NULL)
+ g_string_append_printf (str, "%s ", tmp);
+
+ /* add model */
+ tmp = cd_device_get_model (priv->device);
+ if (tmp != NULL)
+ g_string_append_printf (str, "%s ", tmp);
+
+ /* fall back to _something_ */
+ if (str->len == 0)
+ g_string_append (str, "Profile ");
+
+ /* add the quality */
+ g_string_append_printf (str, "(%s) ",
+ cd_main_quality_to_text (priv->quality));
+
+ /* add date and time */
+ datetime = g_date_time_new_now_utc ();
+ date_str = g_date_time_format (datetime, "%F %H-%M-%S");
+ g_string_append_printf (str, "%s ", date_str);
+ g_free (date_str);
+ g_date_time_unref (datetime);
+
+ /* add the sensor */
+ tmp = cd_sensor_kind_to_string (cd_sensor_get_kind (priv->sensor));
+ if (tmp != NULL)
+ g_string_append_printf (str, "%s ", tmp);
+
+ /* remove trailing space */
+ g_string_set_size (str, str->len - 1);
+
+ /* make suitable filename */
+ g_strdelimit (str->str, "\"*?", '_');
+ priv->basename = g_string_free (str, FALSE);
+}
+
+/**
+ * cd_main_quit_loop_cb:
+ **/
+static gboolean
+cd_main_quit_loop_cb (gpointer user_data)
+{
+ CdMainPrivate *priv = (CdMainPrivate *) user_data;
+ g_main_loop_quit (priv->loop);
+ return FALSE;
+}
+
+/**
+ * cd_main_daemon_method_call:
+ **/
+static void
+cd_main_daemon_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ CdMainPrivate *priv = (CdMainPrivate *) user_data;
+ const gchar *device_id;
+ const gchar *prop_key;
+ const gchar *sensor_id;
+ GError *error = NULL;
+ GVariantIter *iter = NULL;
+ GVariant *prop_value;
+
+ /* should be impossible */
+ if (g_strcmp0 (interface_name, "org.freedesktop.ColorHelper.Display") != 0) {
+ g_dbus_method_invocation_return_error (invocation,
+ CD_MAIN_ERROR,
+ CD_MAIN_ERROR_INTERNAL,
+ "cannot execute method %s on %s",
+ method_name, interface_name);
+ goto out;
+ }
+
+ if (g_strcmp0 (method_name, "Start") == 0) {
+ g_variant_get (parameters, "(&s&sa{sv})",
+ &device_id,
+ &sensor_id,
+ &iter);
+ g_debug ("CdMain: %s:Start(%s,%s)",
+ sender,
+ device_id,
+ sensor_id);
+
+ /* set the default parameters */
+ priv->quality = CD_MAIN_QUALITY_MEDIUM;
+ priv->device_kind = CD_SENSOR_CAP_LCD;
+ while (g_variant_iter_next (iter, "{&sv}",
+ &prop_key, &prop_value)) {
+ if (g_strcmp0 (prop_key, "Quality") == 0) {
+ priv->quality = g_variant_get_uint32 (prop_value);
+ g_debug ("Quality: %s",
+ cd_main_quality_to_text (priv->quality));
+ } else if (g_strcmp0 (prop_key, "Whitepoint") == 0) {
+ priv->target_whitepoint = g_variant_get_uint32 (prop_value);
+ g_debug ("Whitepoint: %iK",
+ priv->target_whitepoint);
+ } else if (g_strcmp0 (prop_key, "Title") == 0) {
+ priv->title = g_variant_dup_string (prop_value, NULL);
+ g_debug ("Title: %s", priv->title);
+ } else if (g_strcmp0 (prop_key, "DeviceKind") == 0) {
+ priv->device_kind = g_variant_get_uint32 (prop_value);
+ g_debug ("Device kind: %s",
+ cd_sensor_cap_to_string (priv->device_kind));
+ } else if (g_strcmp0 (prop_key, "Brightness") == 0) {
+ priv->screen_brightness = g_variant_get_uint32 (prop_value);
+ g_debug ("Device brightness: %i", priv->screen_brightness);
+ } else {
+ /* not a fatal warning */
+ g_warning ("option %s unsupported", prop_key);
+ }
+ }
+
+ /* set a decent default */
+ if (priv->title == NULL)
+ priv->title = g_strdup ("Profile");
+
+ if (priv->status != CD_MAIN_STATUS_IDLE) {
+ g_dbus_method_invocation_return_error (invocation,
+ CD_MAIN_ERROR,
+ CD_MAIN_ERROR_INTERNAL,
+ "cannot start as status is %s",
+ cd_main_status_to_text (priv->status));
+ goto out;
+ }
+
+ /* check the quality argument */
+ if (priv->quality > 2) {
+ g_dbus_method_invocation_return_error (invocation,
+ CD_MAIN_ERROR,
+ CD_MAIN_ERROR_INVALID_VALUE,
+ "invalid quality value %i",
+ priv->quality);
+ goto out;
+ }
+
+ if (priv->target_whitepoint != 0 &&
+ (priv->target_whitepoint < 1000 ||
+ priv->target_whitepoint > 100000)) {
+ g_dbus_method_invocation_return_error (invocation,
+ CD_MAIN_ERROR,
+ CD_MAIN_ERROR_INVALID_VALUE,
+ "invalid target whitepoint value %i",
+ priv->target_whitepoint);
+ goto out;
+ }
+
+ /* watch to see when the sender quits */
+ priv->watcher_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
+ sender,
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ NULL,
+ cd_main_sender_vanished_cb,
+ priv, NULL);
+ priv->status = CD_MAIN_STATUS_IDLE;
+
+ /* start calibration */
+ priv->device = cd_main_find_device (priv,
+ device_id,
+ &error);
+ if (priv->device == NULL) {
+ g_dbus_method_invocation_return_gerror (invocation,
+ error);
+ g_error_free (error);
+ goto out;
+ }
+ priv->sensor = cd_main_find_sensor (priv,
+ sensor_id,
+ &error);
+ if (priv->sensor == NULL) {
+ g_dbus_method_invocation_return_gerror (invocation,
+ error);
+ g_error_free (error);
+ goto out;
+ }
+
+ /* set the filename of all the calibrated files */
+ cd_main_set_basename (priv);
+
+ /* ask the user to attach the device to the screen */
+ priv->status = CD_MAIN_STATUS_WAITING_FOR_INTERACTION;
+ cd_main_emit_interaction_required (priv,
+ CD_MAIN_INTERACTION_CODE_ATTACH_TO_SCREEN);
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ goto out;
+ }
+
+ if (g_strcmp0 (method_name, "Cancel") == 0) {
+ g_debug ("CdMain: %s:Cancel()", sender);
+ if (priv->status != CD_MAIN_STATUS_RUNNING &&
+ priv->status != CD_MAIN_STATUS_WAITING_FOR_INTERACTION) {
+ g_dbus_method_invocation_return_error (invocation,
+ CD_MAIN_ERROR,
+ CD_MAIN_ERROR_INTERNAL,
+ "cannot cancel as status is %s",
+ cd_main_status_to_text (priv->status));
+ goto out;
+ }
+ g_cancellable_cancel (priv->cancellable);
+ priv->status = CD_MAIN_STATUS_IDLE;
+ g_timeout_add (1000, cd_main_quit_loop_cb, priv);
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ goto out;
+ }
+
+ if (g_strcmp0 (method_name, "Resume") == 0) {
+ g_debug ("CdMain: %s:Resume()", sender);
+ if (priv->status != CD_MAIN_STATUS_WAITING_FOR_INTERACTION) {
+ g_dbus_method_invocation_return_error (invocation,
+ CD_MAIN_ERROR,
+ CD_MAIN_ERROR_INTERNAL,
+ "cannot resume as status is %s",
+ cd_main_status_to_text (priv->status));
+ goto out;
+ }
+
+ /* actually start the process now */
+ g_idle_add (cd_main_start_calibration_cb, priv);
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ goto out;
+ }
+
+ /* we suck */
+ g_critical ("failed to process method %s", method_name);
+out:
+ return;
+}
+
+/**
+ * cd_main_daemon_get_property:
+ **/
+static GVariant *
+cd_main_daemon_get_property (GDBusConnection *connection_, const gchar *sender,
+ const gchar *object_path, const gchar *interface_name,
+ const gchar *property_name, GError **error,
+ gpointer user_data)
+{
+ GVariant *retval = NULL;
+ CdMainPrivate *priv = (CdMainPrivate *) user_data;
+
+ /* main interface */
+ if (g_strcmp0 (interface_name,
+ COLOR_HELPER_DBUS_INTERFACE) == 0) {
+ if (g_strcmp0 (property_name, "DaemonVersion") == 0) {
+ retval = g_variant_new_string (VERSION);
+ } else {
+ g_critical ("failed to get %s property %s",
+ interface_name, property_name);
+ }
+ goto out;
+ }
+
+ /* display interface */
+ if (g_strcmp0 (interface_name, COLOR_HELPER_DBUS_INTERFACE_DISPLAY) == 0) {
+ if (g_strcmp0 (property_name, "Progress") == 0) {
+ retval = g_variant_new_uint32 (priv->progress);
+ } else {
+ g_critical ("failed to get %s property %s",
+ interface_name, property_name);
+ }
+ goto out;
+ }
+out:
+ return retval;
+}
+
+/**
+ * cd_main_on_bus_acquired_cb:
+ **/
+static void
+cd_main_on_bus_acquired_cb (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ CdMainPrivate *priv = (CdMainPrivate *) user_data;
+ guint registration_id;
+ guint i;
+ static const GDBusInterfaceVTable interface_vtable = {
+ cd_main_daemon_method_call,
+ cd_main_daemon_get_property,
+ NULL
+ };
+
+ priv->connection = g_object_ref (connection);
+ for (i = 0; i < 2; i++) {
+ registration_id = g_dbus_connection_register_object (connection,
+ COLOR_HELPER_DBUS_PATH,
+ priv->introspection->interfaces[i],
+ &interface_vtable,
+ priv, /* user_data */
+ NULL, /* user_data_free_func */
+ NULL); /* GError** */
+ g_assert (registration_id > 0);
+ }
+}
+
+/**
+ * cd_main_on_name_acquired_cb:
+ **/
+static void
+cd_main_on_name_acquired_cb (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ g_debug ("CdMain: acquired name: %s", name);
+}
+
+/**
+ * cd_main_on_name_lost_cb:
+ **/
+static void
+cd_main_on_name_lost_cb (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ CdMainPrivate *priv = (CdMainPrivate *) user_data;
+ g_debug ("CdMain: lost name: %s", name);
+ g_main_loop_quit (priv->loop);
+}
+
+/**
+ * cd_main_timed_exit_cb:
+ **/
+static gboolean
+cd_main_timed_exit_cb (gpointer user_data)
+{
+ CdMainPrivate *priv = (CdMainPrivate *) user_data;
+ g_main_loop_quit (priv->loop);
+ return FALSE;
+}
+
+/**
+ * cd_main_load_introspection:
+ **/
+static GDBusNodeInfo *
+cd_main_load_introspection (const gchar *filename, GError **error)
+{
+ gboolean ret;
+ gchar *data = NULL;
+ GDBusNodeInfo *info = NULL;
+ GFile *file;
+
+ /* load file */
+ file = g_file_new_for_path (filename);
+ ret = g_file_load_contents (file, NULL, &data,
+ NULL, NULL, error);
+ if (!ret)
+ goto out;
+
+ /* build introspection from XML */
+ info = g_dbus_node_info_new_for_xml (data, error);
+ if (info == NULL)
+ goto out;
+out:
+ g_object_unref (file);
+ g_free (data);
+ return info;
+}
+
+/**
+ * cd_main_emit_property_changed:
+ **/
+static void
+cd_main_emit_property_changed (CdMainPrivate *priv,
+ const gchar *property_name,
+ GVariant *property_value)
+{
+ GVariantBuilder builder;
+ GVariantBuilder invalidated_builder;
+
+ /* build the dict */
+ g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
+ g_variant_builder_add (&builder,
+ "{sv}",
+ property_name,
+ property_value);
+ g_dbus_connection_emit_signal (priv->connection,
+ NULL,
+ COLOR_HELPER_DBUS_PATH,
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged",
+ g_variant_new ("(sa{sv}as)",
+ COLOR_HELPER_DBUS_INTERFACE_DISPLAY,
+ &builder,
+ &invalidated_builder),
+ NULL);
+}
+
+/**
+ * cd_main_percentage_changed_cb:
+ **/
+static void
+cd_main_percentage_changed_cb (CdState *state,
+ guint value,
+ CdMainPrivate *priv)
+{
+ g_debug ("CdMain: Emitting PropertiesChanged(Progress) %i", value);
+ cd_main_emit_property_changed (priv,
+ "Progress",
+ g_variant_new_uint32 (value));
+}
+
+/**
+ * main:
+ **/
+int
+main (int argc, char *argv[])
+{
+ CdMainPrivate *priv;
+ gboolean ret;
+ gboolean timed_exit = FALSE;
+ GError *error = NULL;
+ GOptionContext *context;
+ guint owner_id = 0;
+ guint retval = 1;
+ const GOptionEntry options[] = {
+ { "timed-exit", '\0', 0, G_OPTION_ARG_NONE, &timed_exit,
+ "Exit after a small delay", NULL },
+ { NULL}
+ };
+
+ setlocale (LC_ALL, "");
+
+ g_type_init ();
+ priv = g_new0 (CdMainPrivate, 1);
+ priv->status = CD_MAIN_STATUS_IDLE;
+ priv->interaction_code_last = CD_MAIN_INTERACTION_CODE_NONE;
+ priv->cancellable = g_cancellable_new ();
+ priv->loop = g_main_loop_new (NULL, FALSE);
+
+ /* track progress of the calibration */
+ priv->state = cd_state_new ();
+ cd_state_set_enable_profile (priv->state, TRUE);
+ g_signal_connect (priv->state,
+ "percentage-changed",
+ G_CALLBACK (cd_main_percentage_changed_cb),
+ priv);
+
+ /* TRANSLATORS: program name */
+ g_set_application_name ("Color Management");
+ context = g_option_context_new (NULL);
+ g_option_context_add_main_entries (context, options, NULL);
+ g_option_context_add_group (context, cd_debug_get_option_group ());
+ g_option_context_set_summary (context, "Color Management D-Bus Service");
+ g_option_context_parse (context, &argc, &argv, NULL);
+ g_option_context_free (context);
+
+ /* load introspection from file */
+ priv->introspection = cd_main_load_introspection (DATADIR "/dbus-1/interfaces/"
+ COLOR_HELPER_DBUS_INTERFACE ".xml",
+ &error);
+ if (priv->introspection == NULL) {
+ g_warning ("CdMain: failed to load introspection: %s",
+ error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ /* get client */
+ priv->client = cd_client_new ();
+ ret = cd_client_connect_sync (priv->client, NULL, &error);
+ if (!ret) {
+ g_warning ("failed to contact colord: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ /* own the object */
+ owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+ COLOR_HELPER_DBUS_SERVICE,
+ G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
+ G_BUS_NAME_OWNER_FLAGS_REPLACE,
+ cd_main_on_bus_acquired_cb,
+ cd_main_on_name_acquired_cb,
+ cd_main_on_name_lost_cb,
+ priv, NULL);
+
+ /* Only timeout and close the mainloop if we have specified it
+ * on the command line */
+ if (timed_exit)
+ g_timeout_add_seconds (5, cd_main_timed_exit_cb, priv);
+
+ /* wait */
+ g_main_loop_run (priv->loop);
+
+ /* success */
+ retval = 0;
+out:
+ if (owner_id > 0)
+ g_bus_unown_name (owner_id);
+ g_main_loop_unref (priv->loop);
+ if (priv->client != NULL)
+ g_object_unref (priv->client);
+ if (priv->connection != NULL)
+ g_object_unref (priv->connection);
+ if (priv->introspection != NULL)
+ g_dbus_node_info_unref (priv->introspection);
+ if (priv->array != NULL)
+ g_ptr_array_unref (priv->array);
+ if (priv->sensor != NULL)
+ g_object_unref (priv->sensor);
+ if (priv->device != NULL)
+ g_object_unref (priv->device);
+ if (priv->cancellable != NULL)
+ g_object_unref (priv->cancellable);
+ if (priv->it8_cal != NULL)
+ g_object_unref (priv->it8_cal);
+ if (priv->it8_ti1 != NULL)
+ g_object_unref (priv->it8_ti1);
+ if (priv->it8_ti3 != NULL)
+ g_object_unref (priv->it8_ti3);
+ if (priv->state != NULL)
+ g_object_unref (priv->state);
+ g_free (priv->working_path);
+ g_free (priv->basename);
+ g_free (priv->title);
+ g_free (priv);
+ return retval;
+}
diff --git a/contrib/session-helper/cd-state.c b/contrib/session-helper/cd-state.c
new file mode 100644
index 0000000..e6dfb94
--- /dev/null
+++ b/contrib/session-helper/cd-state.c
@@ -0,0 +1,691 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009-2012 Richard Hughes <richard@hughsie.com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <glib.h>
+#include <signal.h>
+#include <gio/gio.h>
+
+#include "cd-state.h"
+
+#define CD_STATE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CD_TYPE_STATE, CdStatePrivate))
+
+struct _CdStatePrivate
+{
+ gboolean enable_profile;
+ gboolean process_event_sources;
+ gchar *id;
+ gdouble global_share;
+ gdouble *step_profile;
+ GTimer *timer;
+ guint current;
+ guint last_percentage;
+ guint *step_data;
+ guint steps;
+ gulong percentage_child_id;
+ gulong subpercentage_child_id;
+ CdState *child;
+ CdState *parent;
+};
+
+enum {
+ SIGNAL_PERCENTAGE_CHANGED,
+ SIGNAL_SUBPERCENTAGE_CHANGED,
+ SIGNAL_LAST
+};
+
+enum {
+ PROP_0,
+ PROP_SPEED,
+ PROP_LAST
+};
+
+static guint signals [SIGNAL_LAST] = { 0 };
+
+G_DEFINE_TYPE (CdState, cd_state, G_TYPE_OBJECT)
+
+#define CD_STATE_SPEED_SMOOTHING_ITEMS 5
+
+/**
+ * cd_state_error_quark:
+ **/
+GQuark
+cd_state_error_quark (void)
+{
+ static GQuark quark = 0;
+ if (!quark)
+ quark = g_quark_from_static_string ("cd_state_error");
+ return quark;
+}
+
+/**
+ * cd_state_set_enable_profile:
+ **/
+void
+cd_state_set_enable_profile (CdState *state, gboolean enable_profile)
+{
+ g_return_if_fail (CD_IS_STATE (state));
+ state->priv->enable_profile = enable_profile;
+}
+
+/**
+ * cd_state_discrete_to_percent:
+ **/
+static gfloat
+cd_state_discrete_to_percent (guint discrete, guint steps)
+{
+ /* check we are in range */
+ if (discrete > steps)
+ return 100;
+ if (steps == 0) {
+ g_warning ("steps is 0!");
+ return 0;
+ }
+ return ((gfloat) discrete * (100.0f / (gfloat) (steps)));
+}
+
+/**
+ * cd_state_print_parent_chain:
+ **/
+static void
+cd_state_print_parent_chain (CdState *state, guint level)
+{
+ if (state->priv->parent != NULL)
+ cd_state_print_parent_chain (state->priv->parent, level + 1);
+ g_print ("%i) %s (%i/%i)\n",
+ level, state->priv->id, state->priv->current, state->priv->steps);
+}
+
+/**
+ * cd_state_set_percentage:
+ **/
+gboolean
+cd_state_set_percentage (CdState *state, guint percentage)
+{
+ gboolean ret = FALSE;
+
+ /* is it the same */
+ if (percentage == state->priv->last_percentage)
+ goto out;
+
+ /* is it invalid */
+ if (percentage > 100) {
+ cd_state_print_parent_chain (state, 0);
+ g_warning ("percentage %i%% is invalid on %p!",
+ percentage, state);
+ goto out;
+ }
+
+ /* is it less */
+ if (percentage < state->priv->last_percentage) {
+ if (state->priv->enable_profile) {
+ cd_state_print_parent_chain (state, 0);
+ g_critical ("percentage should not go down from %i to %i on %p!",
+ state->priv->last_percentage, percentage, state);
+ }
+ goto out;
+ }
+
+ /* save */
+ state->priv->last_percentage = percentage;
+
+ /* are we so low we don't care */
+ if (state->priv->global_share < 0.001)
+ goto out;
+
+ /* emit */
+ g_signal_emit (state, signals [SIGNAL_PERCENTAGE_CHANGED], 0, percentage);
+
+ /* success */
+ ret = TRUE;
+out:
+ return ret;
+}
+
+/**
+ * cd_state_get_percentage:
+ **/
+guint
+cd_state_get_percentage (CdState *state)
+{
+ return state->priv->last_percentage;
+}
+
+/**
+ * cd_state_set_subpercentage:
+ **/
+static gboolean
+cd_state_set_subpercentage (CdState *state, guint percentage)
+{
+ /* are we so low we don't care */
+ if (state->priv->global_share < 0.01)
+ goto out;
+
+ /* just emit */
+ g_signal_emit (state, signals [SIGNAL_SUBPERCENTAGE_CHANGED], 0, percentage);
+out:
+ return TRUE;
+}
+
+/**
+ * cd_state_child_percentage_changed_cb:
+ **/
+static void
+cd_state_child_percentage_changed_cb (CdState *child, guint percentage, CdState *state)
+{
+ gfloat offset;
+ gfloat range;
+ gfloat extra;
+ guint parent_percentage;
+
+ /* propagate up the stack if CdState has only one step */
+ if (state->priv->steps == 1) {
+ cd_state_set_percentage (state, percentage);
+ return;
+ }
+
+ /* did we call done on a state that did not have a size set? */
+ if (state->priv->steps == 0)
+ return;
+
+ /* always provide two levels of signals */
+ cd_state_set_subpercentage (state, percentage);
+
+ /* already at >= 100% */
+ if (state->priv->current >= state->priv->steps) {
+ g_warning ("already at %i/%i steps on %p", state->priv->current, state->priv->steps, state);
+ return;
+ }
+
+ /* we have to deal with non-linear steps */
+ if (state->priv->step_data != NULL) {
+ /* we don't store zero */
+ if (state->priv->current == 0) {
+ parent_percentage = percentage * state->priv->step_data[state->priv->current] / 100;
+ } else {
+ /* bilinearly interpolate for XXXXXXXXXXXXXXXXXXXX */
+ parent_percentage = (((100 - percentage) * state->priv->step_data[state->priv->current-1]) +
+ (percentage * state->priv->step_data[state->priv->current])) / 100;
+ }
+ goto out;
+ }
+
+ /* get the offset */
+ offset = cd_state_discrete_to_percent (state->priv->current, state->priv->steps);
+
+ /* get the range between the parent step and the next parent step */
+ range = cd_state_discrete_to_percent (state->priv->current+1, state->priv->steps) - offset;
+ if (range < 0.01) {
+ g_warning ("range=%f (from %i to %i), should be impossible", range, state->priv->current+1, state->priv->steps);
+ return;
+ }
+
+ /* get the extra contributed by the child */
+ extra = ((gfloat) percentage / 100.0f) * range;
+
+ /* emit from the parent */
+ parent_percentage = (guint) (offset + extra);
+out:
+ cd_state_set_percentage (state, parent_percentage);
+}
+
+/**
+ * cd_state_child_subpercentage_changed_cb:
+ **/
+static void
+cd_state_child_subpercentage_changed_cb (CdState *child, guint percentage, CdState *state)
+{
+ /* discard this, unless the CdState has only one step */
+ if (state->priv->steps != 1)
+ return;
+
+ /* propagate up the stack as if the parent didn't exist */
+ cd_state_set_subpercentage (state, percentage);
+}
+
+/**
+ * cd_state_reset:
+ **/
+gboolean
+cd_state_reset (CdState *state)
+{
+ gboolean ret = TRUE;
+
+ g_return_val_if_fail (CD_IS_STATE (state), FALSE);
+
+ /* reset values */
+ state->priv->steps = 0;
+ state->priv->current = 0;
+ state->priv->last_percentage = 0;
+
+ /* only use the timer if profiling; it's expensive */
+ if (state->priv->enable_profile)
+ g_timer_start (state->priv->timer);
+
+ /* disconnect client */
+ if (state->priv->percentage_child_id != 0) {
+ g_signal_handler_disconnect (state->priv->child,
+ state->priv->percentage_child_id);
+ state->priv->percentage_child_id = 0;
+ }
+ if (state->priv->subpercentage_child_id != 0) {
+ g_signal_handler_disconnect (state->priv->child,
+ state->priv->subpercentage_child_id);
+ state->priv->subpercentage_child_id = 0;
+ }
+
+ /* unref child */
+ if (state->priv->child != NULL) {
+ g_object_unref (state->priv->child);
+ state->priv->child = NULL;
+ }
+
+ /* no more step data */
+ g_free (state->priv->step_data);
+ g_free (state->priv->step_profile);
+ state->priv->step_data = NULL;
+ state->priv->step_profile = NULL;
+ return ret;
+}
+
+/**
+ * cd_state_set_global_share:
+ **/
+static void
+cd_state_set_global_share (CdState *state, gdouble global_share)
+{
+ state->priv->global_share = global_share;
+}
+
+/**
+ * cd_state_get_child:
+ **/
+CdState *
+cd_state_get_child (CdState *state)
+{
+ CdState *child = NULL;
+
+ g_return_val_if_fail (CD_IS_STATE (state), NULL);
+
+ /* already set child */
+ if (state->priv->child != NULL) {
+ g_signal_handler_disconnect (state->priv->child,
+ state->priv->percentage_child_id);
+ g_signal_handler_disconnect (state->priv->child,
+ state->priv->subpercentage_child_id);
+ g_object_unref (state->priv->child);
+ }
+
+ /* connect up signals */
+ child = cd_state_new ();
+ child->priv->parent = state; /* do not ref! */
+ state->priv->child = child;
+ state->priv->percentage_child_id =
+ g_signal_connect (child, "percentage-changed",
+ G_CALLBACK (cd_state_child_percentage_changed_cb),
+ state);
+ state->priv->subpercentage_child_id =
+ g_signal_connect (child, "subpercentage-changed",
+ G_CALLBACK (cd_state_child_subpercentage_changed_cb),
+ state);
+ /* reset child */
+ child->priv->current = 0;
+ child->priv->last_percentage = 0;
+
+ /* set the global share on the new child */
+ cd_state_set_global_share (child, state->priv->global_share);
+
+ /* set the profile state */
+ cd_state_set_enable_profile (child,
+ state->priv->enable_profile);
+
+ return child;
+}
+
+/**
+ * cd_state_set_number_steps_real:
+ **/
+gboolean
+cd_state_set_number_steps_real (CdState *state, guint steps, const gchar *strloc)
+{
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail (state != NULL, FALSE);
+
+ /* nothing to do for 0 steps */
+ if (steps == 0) {
+ ret = TRUE;
+ goto out;
+ }
+
+ /* did we call done on a state that did not have a size set? */
+ if (state->priv->steps != 0) {
+ g_warning ("steps already set to %i, can't set %i! [%s]",
+ state->priv->steps, steps, strloc);
+ cd_state_print_parent_chain (state, 0);
+ goto out;
+ }
+
+ /* set id */
+ g_free (state->priv->id);
+ state->priv->id = g_strdup_printf ("%s", strloc);
+
+ /* only use the timer if profiling; it's expensive */
+ if (state->priv->enable_profile)
+ g_timer_start (state->priv->timer);
+
+ /* imply reset */
+ cd_state_reset (state);
+
+ /* set steps */
+ state->priv->steps = steps;
+
+ /* global share just got smaller */
+ state->priv->global_share /= steps;
+
+ /* success */
+ ret = TRUE;
+out:
+ return ret;
+}
+
+/**
+ * cd_state_set_steps_real:
+ **/
+gboolean
+cd_state_set_steps_real (CdState *state, GError **error, const gchar *strloc, gint value, ...)
+{
+ va_list args;
+ guint i;
+ gint value_temp;
+ guint total;
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail (state != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ /* we must set at least one thing */
+ total = value;
+
+ /* process the valist */
+ va_start (args, value);
+ for (i=0;; i++) {
+ value_temp = va_arg (args, gint);
+ if (value_temp == -1)
+ break;
+ total += (guint) value_temp;
+ }
+ va_end (args);
+
+ /* does not sum to 100% */
+ if (total != 100) {
+ g_set_error (error,
+ CD_STATE_ERROR,
+ CD_STATE_ERROR_INVALID,
+ "percentage not 100: %i",
+ total);
+ goto out;
+ }
+
+ /* set step number */
+ ret = cd_state_set_number_steps_real (state, i+1, strloc);
+ if (!ret) {
+ g_set_error (error,
+ CD_STATE_ERROR,
+ CD_STATE_ERROR_INVALID,
+ "failed to set number steps: %i",
+ i+1);
+ goto out;
+ }
+
+ /* save this data */
+ total = value;
+ state->priv->step_data = g_new0 (guint, i+2);
+ state->priv->step_profile = g_new0 (gdouble, i+2);
+ state->priv->step_data[0] = total;
+ va_start (args, value);
+ for (i=0;; i++) {
+ value_temp = va_arg (args, gint);
+ if (value_temp == -1)
+ break;
+
+ /* we pre-add the data to make access simpler */
+ total += (guint) value_temp;
+ state->priv->step_data[i+1] = total;
+ }
+ va_end (args);
+
+ /* success */
+ ret = TRUE;
+out:
+ return ret;
+}
+
+/**
+ * cd_state_show_profile:
+ **/
+static void
+cd_state_show_profile (CdState *state)
+{
+ gdouble division;
+ gdouble total_time = 0.0f;
+ GString *result;
+ guint i;
+ guint uncumalitive = 0;
+
+ /* get the total time so we can work out the divisor */
+ for (i = 0; i < state->priv->steps; i++)
+ total_time += state->priv->step_profile[i];
+ division = total_time / 100.0f;
+
+ /* what we set */
+ result = g_string_new ("steps were set as [ ");
+ for (i = 0; i < state->priv->steps; i++) {
+ g_string_append_printf (result, "%i, ",
+ state->priv->step_data[i] - uncumalitive);
+ uncumalitive = state->priv->step_data[i];
+ }
+
+ /* what we _should_ have set */
+ g_string_append_printf (result, "-1 ] but should have been: [ ");
+ for (i = 0; i < state->priv->steps; i++) {
+ g_string_append_printf (result, "%.0f, ",
+ state->priv->step_profile[i] / division);
+ }
+ g_printerr ("\n\n%s-1 ] at %s\n\n", result->str, state->priv->id);
+ g_string_free (result, TRUE);
+}
+
+/**
+ * cd_state_done_real:
+ **/
+gboolean
+cd_state_done_real (CdState *state, GError **error, const gchar *strloc)
+{
+ gboolean ret;
+ gdouble elapsed;
+ gfloat percentage;
+
+ g_return_val_if_fail (state != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ /* did we call done on a state that did not have a size set? */
+ if (state->priv->steps == 0) {
+ g_set_error (error, CD_STATE_ERROR, CD_STATE_ERROR_INVALID,
+ "done on a state %p that did not have a size set! [%s]",
+ state, strloc);
+ cd_state_print_parent_chain (state, 0);
+ ret = FALSE;
+ goto out;
+ }
+
+ /* save the step interval for profiling */
+ if (state->priv->enable_profile) {
+ /* save the duration in the array */
+ elapsed = g_timer_elapsed (state->priv->timer, NULL);
+ if (state->priv->step_profile != NULL)
+ state->priv->step_profile[state->priv->current] = elapsed;
+ g_timer_start (state->priv->timer);
+ }
+
+ /* is already at 100%? */
+ if (state->priv->current >= state->priv->steps) {
+ g_set_error (error, CD_STATE_ERROR, CD_STATE_ERROR_INVALID,
+ "already at 100%% state [%s]", strloc);
+ cd_state_print_parent_chain (state, 0);
+ ret = FALSE;
+ goto out;
+ }
+
+ /* is child not at 100%? */
+ if (state->priv->child != NULL) {
+ CdStatePrivate *child_priv = state->priv->child->priv;
+ if (child_priv->current != child_priv->steps) {
+ g_print ("child is at %i/%i steps and parent done [%s]\n",
+ child_priv->current, child_priv->steps, strloc);
+ cd_state_print_parent_chain (state->priv->child, 0);
+ ret = TRUE;
+ /* do not abort, as we want to clean this up */
+ }
+ }
+
+ /* another */
+ state->priv->current++;
+
+ /* find new percentage */
+ if (state->priv->step_data == NULL) {
+ percentage = cd_state_discrete_to_percent (state->priv->current,
+ state->priv->steps);
+ } else {
+ /* this is cumalative, for XXXXXXXXXXXXXXXXXXXXy access */
+ percentage = state->priv->step_data[state->priv->current - 1];
+ }
+ cd_state_set_percentage (state, (guint) percentage);
+
+ /* show any profiling stats */
+ if (state->priv->enable_profile &&
+ state->priv->current == state->priv->steps &&
+ state->priv->step_profile != NULL) {
+ cd_state_show_profile (state);
+ }
+
+ /* reset child if it exists */
+ if (state->priv->child != NULL)
+ cd_state_reset (state->priv->child);
+out:
+ return ret;
+}
+
+/**
+ * cd_state_finished_real:
+ **/
+gboolean
+cd_state_finished_real (CdState *state, GError **error, const gchar *strloc)
+{
+ gboolean ret;
+
+ g_return_val_if_fail (state != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ /* is already at 100%? */
+ if (state->priv->current == state->priv->steps)
+ goto out;
+
+ /* all done */
+ state->priv->current = state->priv->steps;
+
+ /* set new percentage */
+ cd_state_set_percentage (state, 100);
+out:
+ return ret;
+}
+
+/**
+ * cd_state_finalize:
+ **/
+static void
+cd_state_finalize (GObject *object)
+{
+ CdState *state;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (CD_IS_STATE (object));
+ state = CD_STATE (object);
+
+ cd_state_reset (state);
+ g_free (state->priv->id);
+ g_free (state->priv->step_data);
+ g_free (state->priv->step_profile);
+ g_timer_destroy (state->priv->timer);
+
+ G_OBJECT_CLASS (cd_state_parent_class)->finalize (object);
+}
+
+/**
+ * cd_state_class_init:
+ **/
+static void
+cd_state_class_init (CdStateClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = cd_state_finalize;
+
+ signals [SIGNAL_PERCENTAGE_CHANGED] =
+ g_signal_new ("percentage-changed",
+ G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CdStateClass, percentage_changed),
+ NULL, NULL, g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1, G_TYPE_UINT);
+
+ signals [SIGNAL_SUBPERCENTAGE_CHANGED] =
+ g_signal_new ("subpercentage-changed",
+ G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CdStateClass, subpercentage_changed),
+ NULL, NULL, g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1, G_TYPE_UINT);
+
+ g_type_class_add_private (klass, sizeof (CdStatePrivate));
+}
+
+/**
+ * cd_state_init:
+ **/
+static void
+cd_state_init (CdState *state)
+{
+ state->priv = CD_STATE_GET_PRIVATE (state);
+ state->priv->global_share = 1.0f;
+ state->priv->timer = g_timer_new ();
+}
+
+/**
+ * cd_state_new:
+ **/
+CdState *
+cd_state_new (void)
+{
+ CdState *state;
+ state = g_object_new (CD_TYPE_STATE, NULL);
+ return CD_STATE (state);
+}
diff --git a/contrib/session-helper/cd-state.h b/contrib/session-helper/cd-state.h
new file mode 100644
index 0000000..56f4cde
--- /dev/null
+++ b/contrib/session-helper/cd-state.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2009-2012 Richard Hughes <richard@hughsie.com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __CD_STATE_H
+#define __CD_STATE_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define CD_TYPE_STATE (cd_state_get_type ())
+#define CD_STATE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CD_TYPE_STATE, CdState))
+#define CD_STATE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), CD_TYPE_STATE, CdStateClass))
+#define CD_IS_STATE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CD_TYPE_STATE))
+#define CD_IS_STATE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CD_TYPE_STATE))
+#define CD_STATE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CD_TYPE_STATE, CdStateClass))
+#define CD_STATE_ERROR (cd_state_error_quark ())
+
+typedef struct _CdState CdState;
+typedef struct _CdStatePrivate CdStatePrivate;
+typedef struct _CdStateClass CdStateClass;
+
+struct _CdState
+{
+ GObject parent;
+ CdStatePrivate *priv;
+};
+
+struct _CdStateClass
+{
+ GObjectClass parent_class;
+ void (* percentage_changed) (CdState *state,
+ guint value);
+ void (* subpercentage_changed) (CdState *state,
+ guint value);
+};
+
+typedef enum {
+ CD_STATE_ERROR_CANCELLED,
+ CD_STATE_ERROR_INVALID,
+ CD_STATE_ERROR_LAST
+} CdStateError;
+
+#define cd_state_done(state, error) cd_state_done_real(state, error, G_STRLOC)
+#define cd_state_finished(state, error) cd_state_finished_real(state, error, G_STRLOC)
+#define cd_state_set_number_steps(state, steps) cd_state_set_number_steps_real(state, steps, G_STRLOC)
+#define cd_state_set_steps(state, error, value, args...) cd_state_set_steps_real(state, error, G_STRLOC, value, ## args)
+
+GType cd_state_get_type (void);
+GQuark cd_state_error_quark (void);
+CdState *cd_state_new (void);
+CdState *cd_state_get_child (CdState *state);
+
+/* percentage changed */
+gboolean cd_state_set_number_steps_real (CdState *state,
+ guint steps,
+ const gchar *strloc);
+gboolean cd_state_set_steps_real (CdState *state,
+ GError **error,
+ const gchar *strloc,
+ gint value, ...);
+gboolean cd_state_set_percentage (CdState *state,
+ guint percentage);
+guint cd_state_get_percentage (CdState *state);
+gboolean cd_state_reset (CdState *state);
+gboolean cd_state_done_real (CdState *state,
+ GError **error,
+ const gchar *strloc)
+ G_GNUC_WARN_UNUSED_RESULT;
+gboolean cd_state_finished_real (CdState *state,
+ GError **error,
+ const gchar *strloc)
+ G_GNUC_WARN_UNUSED_RESULT;
+void cd_state_set_enable_profile (CdState *state,
+ gboolean enable_profile);
+
+G_END_DECLS
+
+#endif /* __CD_STATE_H */
+
diff --git a/contrib/session-helper/org.freedesktop.ColorHelper.service.in b/contrib/session-helper/org.freedesktop.ColorHelper.service.in
new file mode 100644
index 0000000..abcfb7f
--- /dev/null
+++ b/contrib/session-helper/org.freedesktop.ColorHelper.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.freedesktop.ColorHelper
+Exec=@servicedir@/colord-session
diff --git a/contrib/session-helper/org.freedesktop.ColorHelper.xml b/contrib/session-helper/org.freedesktop.ColorHelper.xml
new file mode 100644
index 0000000..6d0a5ad
--- /dev/null
+++ b/contrib/session-helper/org.freedesktop.ColorHelper.xml
@@ -0,0 +1,249 @@
+<!DOCTYPE node PUBLIC
+"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
+ <interface name='org.freedesktop.ColorHelper'>
+ <doc:doc>
+ <doc:description>
+ <doc:para>
+ The interface used for general calibration queries not related
+ to specific device types, for example displays.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+
+ <!--***********************************************************-->
+ <property name='DaemonVersion' type='s' access='read'>
+ <doc:doc>
+ <doc:description>
+ <doc:para>
+ The daemon version.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ </property>
+ </interface>
+
+ <interface name='org.freedesktop.ColorHelper.Display'>
+ <doc:doc>
+ <doc:description>
+ <doc:para>
+ The interface used for calibrating displays.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+
+ <!--***********************************************************-->
+ <property name='Progress' type='u' access='read'>
+ <doc:doc>
+ <doc:description>
+ <doc:para>
+ The percentage complete of the calibration.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ </property>
+
+ <!--***********************************************************-->
+ <method name='Start'>
+ <doc:doc>
+ <doc:description>
+ <doc:para>
+ Starts the calibration.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ <arg type='s' name='device_id' direction='in'>
+ <doc:doc>
+ <doc:summary>
+ <doc:para>
+ The device ID, e.g. <doc:tt>xrandr-LVDS1</doc:tt>.
+ </doc:para>
+ </doc:summary>
+ </doc:doc>
+ </arg>
+ <arg type='s' name='profile_id' direction='in'>
+ <doc:doc>
+ <doc:summary>
+ <doc:para>
+ The profile ID, e.g. <doc:tt>sensor-colorhug</doc:tt>.
+ </doc:para>
+ </doc:summary>
+ </doc:doc>
+ </arg>
+ <arg type='a{sv}' name='options' direction='in'>
+ <doc:doc>
+ <doc:summary>
+ <doc:para>
+ Optional parameters that will influence the calibration.
+ Known keys are:
+ <doc:tt>Quality</doc:tt> : (u) The calibration quality, where 0 is low, 1 is medium and
+ 2 is high.
+ <doc:tt>Whitepoint</doc:tt> : (u) The target whitepoint in Kelvin, or 0 or native.
+ <doc:tt>Title</doc:tt>doc:tt> : (s) The profile title, e.g. <doc:tt>Lenovo T61</doc:tt>.
+ <doc:tt>DeviceKind</doc:tt> : (u) The CdSensorCap for the display.
+ <doc:tt>Brightness</doc:tt> : (u) The display brightness.
+ </doc:para>
+ </doc:summary>
+ </doc:doc>
+ </arg>
+ </method>
+
+ <!--***********************************************************-->
+ <method name='Cancel'>
+ <doc:doc>
+ <doc:description>
+ <doc:para>
+ Cancel the calibration that is in progress.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ <!--***********************************************************-->
+ <method name='Resume'>
+ <doc:doc>
+ <doc:description>
+ <doc:para>
+ Resume the calibration that is in progress.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ </method>
+
+ <!-- ************************************************************ -->
+ <signal name='Finished'>
+ <doc:doc>
+ <doc:description>
+ <doc:para>
+ The calibration process has been completed.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ <arg type='u' name='error_code' direction='out'>
+ <doc:doc>
+ <doc:summary>
+ <doc:para>
+ An error code, or 0 for no error. The error code is
+ specified in CdMainError.
+ </doc:para>
+ </doc:summary>
+ </doc:doc>
+ </arg>
+ <arg type='s' name='error_details' direction='out'>
+ <doc:doc>
+ <doc:summary>
+ <doc:para>
+ Any additional (untranslated) error output for debugging.
+ </doc:para>
+ </doc:summary>
+ </doc:doc>
+ </arg>
+ </signal>
+
+ <!-- ************************************************************ -->
+ <signal name='UpdateSample'>
+ <doc:doc>
+ <doc:description>
+ <doc:para>
+ Emitted when the controller should update the RGB patch on
+ the screen. The controller has 100ms to update the display.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ <arg type='d' name='red' direction='out'>
+ <doc:doc>
+ <doc:summary>
+ <doc:para>
+ The red value from 0.0 to 1.0
+ </doc:para>
+ </doc:summary>
+ </doc:doc>
+ </arg>
+ <arg type='d' name='green' direction='out'>
+ <doc:doc>
+ <doc:summary>
+ <doc:para>
+ The red value from 0.0 to 1.0
+ </doc:para>
+ </doc:summary>
+ </doc:doc>
+ </arg>
+ <arg type='d' name='blue' direction='out'>
+ <doc:doc>
+ <doc:summary>
+ <doc:para>
+ The red value from 0.0 to 1.0
+ </doc:para>
+ </doc:summary>
+ </doc:doc>
+ </arg>
+ </signal>
+
+ <!-- ************************************************************ -->
+ <signal name='UpdateGamma'>
+ <doc:doc>
+ <doc:description>
+ <doc:para>
+ Emitted when the controller should update the gamma ramps
+ for the screen.
+ The controller has 50ms to update the display.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ <arg type='a(ddd)' name='gamma' direction='out'>
+ <doc:doc>
+ <doc:summary>
+ <doc:para>
+ The red, green and blue ramps, which should be
+ interpolated to match the display gamma size.
+ </doc:para>
+ </doc:summary>
+ </doc:doc>
+ </arg>
+ </signal>
+
+ <!-- ************************************************************ -->
+ <signal name='InteractionRequired'>
+ <doc:doc>
+ <doc:description>
+ <doc:para>
+ Emitted when the user needs to do something.
+ </doc:para>
+ </doc:description>
+ </doc:doc>
+ <arg type='u' name='code' direction='out'>
+ <doc:doc>
+ <doc:summary>
+ <doc:para>
+ The interaction code, where:
+ 0 = attach to screen
+ 1 = move to calibration mode.
+ 2 = move to surface mode.
+ </doc:para>
+ </doc:summary>
+ </doc:doc>
+ </arg>
+ <arg type='s' name='message' direction='out'>
+ <doc:doc>
+ <doc:summary>
+ <doc:para>
+ Any debugging message for the interaction.
+ </doc:para>
+ </doc:summary>
+ </doc:doc>
+ </arg>
+ <arg type='s' name='image' direction='out'>
+ <doc:doc>
+ <doc:summary>
+ <doc:para>
+ An image to show what interaction is required, or '' for
+ no image available.
+ </doc:para>
+ </doc:summary>
+ </doc:doc>
+ </arg>
+ </signal>
+
+ </interface>
+</node>