~ctf/unity-settings-daemon/bug1389099_mic_volume_icons

« back to all changes in this revision

Viewing changes to plugins/color/gsd-color-manager.c

  • Committer: Package Import Robot
  • Author(s): Robert Ancell
  • Date: 2014-02-07 11:44:36 UTC
  • Revision ID: package-import@ubuntu.com-20140207114436-7t5u3yvwc4ul7w3e
Tags: upstream-14.04.0
ImportĀ upstreamĀ versionĀ 14.04.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
 
2
 *
 
3
 * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
 
4
 * Copyright (C) 2011 Richard Hughes <richard@hughsie.com>
 
5
 *
 
6
 * This program is free software; you can redistribute it and/or modify
 
7
 * it under the terms of the GNU General Public License as published by
 
8
 * the Free Software Foundation; either version 2 of the License, or
 
9
 * (at your option) any later version.
 
10
 *
 
11
 * This program is distributed in the hope that it will be useful,
 
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
 * GNU General Public License for more details.
 
15
 *
 
16
 * You should have received a copy of the GNU General Public License
 
17
 * along with this program; if not, write to the Free Software
 
18
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
19
 *
 
20
 */
 
21
 
 
22
#include "config.h"
 
23
 
 
24
#include <glib/gi18n.h>
 
25
#include <colord.h>
 
26
#include <libnotify/notify.h>
 
27
#include <gdk/gdk.h>
 
28
#include <stdlib.h>
 
29
#include <lcms2.h>
 
30
#include <canberra-gtk.h>
 
31
 
 
32
#define GNOME_DESKTOP_USE_UNSTABLE_API
 
33
#include <libgnome-desktop/gnome-rr.h>
 
34
 
 
35
#include "gnome-settings-plugin.h"
 
36
#include "gnome-settings-profile.h"
 
37
#include "gnome-settings-session.h"
 
38
#include "gsd-color-manager.h"
 
39
#include "gcm-profile-store.h"
 
40
#include "gcm-dmi.h"
 
41
#include "gcm-edid.h"
 
42
 
 
43
#define GSD_COLOR_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSD_TYPE_COLOR_MANAGER, GsdColorManagerPrivate))
 
44
 
 
45
#define GCM_SESSION_NOTIFY_TIMEOUT                      30000 /* ms */
 
46
#define GCM_SETTINGS_RECALIBRATE_PRINTER_THRESHOLD      "recalibrate-printer-threshold"
 
47
#define GCM_SETTINGS_RECALIBRATE_DISPLAY_THRESHOLD      "recalibrate-display-threshold"
 
48
 
 
49
struct GsdColorManagerPrivate
 
50
{
 
51
        GDBusProxy      *session;
 
52
        CdClient        *client;
 
53
        GSettings       *settings;
 
54
        GcmProfileStore *profile_store;
 
55
        GcmDmi          *dmi;
 
56
        GnomeRRScreen   *x11_screen;
 
57
        GHashTable      *edid_cache;
 
58
        GdkWindow       *gdk_window;
 
59
        gboolean         session_is_active;
 
60
        GHashTable      *device_assign_hash;
 
61
};
 
62
 
 
63
enum {
 
64
        PROP_0,
 
65
};
 
66
 
 
67
static void     gsd_color_manager_class_init  (GsdColorManagerClass *klass);
 
68
static void     gsd_color_manager_init        (GsdColorManager      *color_manager);
 
69
static void     gsd_color_manager_finalize    (GObject             *object);
 
70
 
 
71
G_DEFINE_TYPE (GsdColorManager, gsd_color_manager, G_TYPE_OBJECT)
 
72
 
 
73
static gpointer manager_object = NULL;
 
74
 
 
75
/* see http://www.oyranos.org/wiki/index.php?title=ICC_Profiles_in_X_Specification_0.3 */
 
76
#define GCM_ICC_PROFILE_IN_X_VERSION_MAJOR      0
 
77
#define GCM_ICC_PROFILE_IN_X_VERSION_MINOR      3
 
78
 
 
79
typedef struct {
 
80
        guint32          red;
 
81
        guint32          green;
 
82
        guint32          blue;
 
83
} GnomeRROutputClutItem;
 
84
 
 
85
GQuark
 
86
gsd_color_manager_error_quark (void)
 
87
{
 
88
        static GQuark quark = 0;
 
89
        if (!quark)
 
90
                quark = g_quark_from_static_string ("gsd_color_manager_error");
 
91
        return quark;
 
92
}
 
93
 
 
94
static GcmEdid *
 
95
gcm_session_get_output_edid (GsdColorManager *manager, GnomeRROutput *output, GError **error)
 
96
{
 
97
        const guint8 *data;
 
98
        gsize size;
 
99
        GcmEdid *edid = NULL;
 
100
        gboolean ret;
 
101
 
 
102
        /* can we find it in the cache */
 
103
        edid = g_hash_table_lookup (manager->priv->edid_cache,
 
104
                                    gnome_rr_output_get_name (output));
 
105
        if (edid != NULL) {
 
106
                g_object_ref (edid);
 
107
                goto out;
 
108
        }
 
109
 
 
110
        /* parse edid */
 
111
        data = gnome_rr_output_get_edid_data (output, &size);
 
112
        if (data == NULL || size == 0) {
 
113
                g_set_error_literal (error,
 
114
                                     GNOME_RR_ERROR,
 
115
                                     GNOME_RR_ERROR_UNKNOWN,
 
116
                                     "unable to get EDID for output");
 
117
                goto out;
 
118
        }
 
119
        edid = gcm_edid_new ();
 
120
        ret = gcm_edid_parse (edid, data, size, error);
 
121
        if (!ret) {
 
122
                g_object_unref (edid);
 
123
                edid = NULL;
 
124
                goto out;
 
125
        }
 
126
 
 
127
        /* add to cache */
 
128
        g_hash_table_insert (manager->priv->edid_cache,
 
129
                             g_strdup (gnome_rr_output_get_name (output)),
 
130
                             g_object_ref (edid));
 
131
out:
 
132
        return edid;
 
133
}
 
134
 
 
135
static gboolean
 
136
gcm_session_screen_set_icc_profile (GsdColorManager *manager,
 
137
                                    const gchar *filename,
 
138
                                    GError **error)
 
139
{
 
140
        gboolean ret;
 
141
        gchar *data = NULL;
 
142
        gsize length;
 
143
        guint version_data;
 
144
        GsdColorManagerPrivate *priv = manager->priv;
 
145
 
 
146
        g_return_val_if_fail (filename != NULL, FALSE);
 
147
 
 
148
        g_debug ("setting root window ICC profile atom from %s", filename);
 
149
 
 
150
        /* get contents of file */
 
151
        ret = g_file_get_contents (filename, &data, &length, error);
 
152
        if (!ret)
 
153
                goto out;
 
154
 
 
155
        /* set profile property */
 
156
        gdk_property_change (priv->gdk_window,
 
157
                             gdk_atom_intern_static_string ("_ICC_PROFILE"),
 
158
                             gdk_atom_intern_static_string ("CARDINAL"),
 
159
                             8,
 
160
                             GDK_PROP_MODE_REPLACE,
 
161
                             (const guchar *) data, length);
 
162
 
 
163
        /* set version property */
 
164
        version_data = GCM_ICC_PROFILE_IN_X_VERSION_MAJOR * 100 +
 
165
                        GCM_ICC_PROFILE_IN_X_VERSION_MINOR * 1;
 
166
        gdk_property_change (priv->gdk_window,
 
167
                             gdk_atom_intern_static_string ("_ICC_PROFILE_IN_X_VERSION"),
 
168
                             gdk_atom_intern_static_string ("CARDINAL"),
 
169
                             8,
 
170
                             GDK_PROP_MODE_REPLACE,
 
171
                             (const guchar *) &version_data, 1);
 
172
out:
 
173
        g_free (data);
 
174
        return ret;
 
175
}
 
176
 
 
177
static gchar *
 
178
gcm_session_get_output_id (GsdColorManager *manager, GnomeRROutput *output)
 
179
{
 
180
        const gchar *name;
 
181
        const gchar *serial;
 
182
        const gchar *vendor;
 
183
        GcmEdid *edid = NULL;
 
184
        GString *device_id;
 
185
        GError *error = NULL;
 
186
 
 
187
        /* all output devices are prefixed with this */
 
188
        device_id = g_string_new ("xrandr");
 
189
 
 
190
        /* get the output EDID if possible */
 
191
        edid = gcm_session_get_output_edid (manager, output, &error);
 
192
        if (edid == NULL) {
 
193
                g_debug ("no edid for %s [%s], falling back to connection name",
 
194
                         gnome_rr_output_get_name (output),
 
195
                         error->message);
 
196
                g_error_free (error);
 
197
                g_string_append_printf (device_id,
 
198
                                        "-%s",
 
199
                                        gnome_rr_output_get_name (output));
 
200
                goto out;
 
201
        }
 
202
 
 
203
        /* check EDID data is okay to use */
 
204
        vendor = gcm_edid_get_vendor_name (edid);
 
205
        name = gcm_edid_get_monitor_name (edid);
 
206
        serial = gcm_edid_get_serial_number (edid);
 
207
        if (vendor == NULL && name == NULL && serial == NULL) {
 
208
                g_debug ("edid invalid for %s, falling back to connection name",
 
209
                         gnome_rr_output_get_name (output));
 
210
                g_string_append_printf (device_id,
 
211
                                        "-%s",
 
212
                                        gnome_rr_output_get_name (output));
 
213
                goto out;
 
214
        }
 
215
 
 
216
        /* use EDID data */
 
217
        if (vendor != NULL)
 
218
                g_string_append_printf (device_id, "-%s", vendor);
 
219
        if (name != NULL)
 
220
                g_string_append_printf (device_id, "-%s", name);
 
221
        if (serial != NULL)
 
222
                g_string_append_printf (device_id, "-%s", serial);
 
223
out:
 
224
        if (edid != NULL)
 
225
                g_object_unref (edid);
 
226
        return g_string_free (device_id, FALSE);
 
227
}
 
228
 
 
229
typedef struct {
 
230
        GsdColorManager         *manager;
 
231
        CdProfile               *profile;
 
232
        CdDevice                *device;
 
233
        guint32                  output_id;
 
234
} GcmSessionAsyncHelper;
 
235
 
 
236
static void
 
237
gcm_session_async_helper_free (GcmSessionAsyncHelper *helper)
 
238
{
 
239
        if (helper->manager != NULL)
 
240
                g_object_unref (helper->manager);
 
241
        if (helper->profile != NULL)
 
242
                g_object_unref (helper->profile);
 
243
        if (helper->device != NULL)
 
244
                g_object_unref (helper->device);
 
245
        g_free (helper);
 
246
}
 
247
 
 
248
static cmsBool
 
249
_cmsWriteTagTextAscii (cmsHPROFILE lcms_profile,
 
250
                       cmsTagSignature sig,
 
251
                       const gchar *text)
 
252
{
 
253
        cmsBool ret;
 
254
        cmsMLU *mlu = cmsMLUalloc (0, 1);
 
255
        cmsMLUsetASCII (mlu, "en", "US", text);
 
256
        ret = cmsWriteTag (lcms_profile, sig, mlu);
 
257
        cmsMLUfree (mlu);
 
258
        return ret;
 
259
}
 
260
 
 
261
static gboolean
 
262
gcm_utils_mkdir_for_filename (const gchar *filename, GError **error)
 
263
{
 
264
        gboolean ret = FALSE;
 
265
        GFile *file;
 
266
        GFile *parent_dir = NULL;
 
267
 
 
268
        /* get parent directory */
 
269
        file = g_file_new_for_path (filename);
 
270
        parent_dir = g_file_get_parent (file);
 
271
        if (parent_dir == NULL) {
 
272
                g_set_error (error,
 
273
                             GSD_COLOR_MANAGER_ERROR,
 
274
                             GSD_COLOR_MANAGER_ERROR_FAILED,
 
275
                             "could not get parent dir %s",
 
276
                             filename);
 
277
                goto out;
 
278
        }
 
279
 
 
280
        /* ensure desination does not already exist */
 
281
        ret = g_file_query_exists (parent_dir, NULL);
 
282
        if (ret)
 
283
                goto out;
 
284
        ret = g_file_make_directory_with_parents (parent_dir, NULL, error);
 
285
        if (!ret)
 
286
                goto out;
 
287
out:
 
288
        if (file != NULL)
 
289
                g_object_unref (file);
 
290
        if (parent_dir != NULL)
 
291
                g_object_unref (parent_dir);
 
292
        return ret;
 
293
}
 
294
 
 
295
#ifdef HAVE_NEW_LCMS
 
296
static wchar_t *
 
297
utf8_to_wchar_t (const char *src)
 
298
{
 
299
        size_t len;
 
300
        size_t converted;
 
301
        wchar_t *buf = NULL;
 
302
 
 
303
        len = mbstowcs (NULL, src, 0);
 
304
        if (len == (size_t) -1) {
 
305
                g_warning ("Invalid UTF-8 in string %s", src);
 
306
                goto out;
 
307
        }
 
308
        len += 1;
 
309
        buf = g_malloc (sizeof (wchar_t) * len);
 
310
        converted = mbstowcs (buf, src, len - 1);
 
311
        g_assert (converted != -1);
 
312
        buf[converted] = '\0';
 
313
out:
 
314
        return buf;
 
315
}
 
316
 
 
317
static cmsBool
 
318
_cmsDictAddEntryAscii (cmsHANDLE dict,
 
319
                       const gchar *key,
 
320
                       const gchar *value)
 
321
{
 
322
        cmsBool ret = FALSE;
 
323
        wchar_t *mb_key = NULL;
 
324
        wchar_t *mb_value = NULL;
 
325
 
 
326
        mb_key = utf8_to_wchar_t (key);
 
327
        if (mb_key == NULL)
 
328
                goto out;
 
329
        mb_value = utf8_to_wchar_t (value);
 
330
        if (mb_value == NULL)
 
331
                goto out;
 
332
        ret = cmsDictAddEntry (dict, mb_key, mb_value, NULL, NULL);
 
333
out:
 
334
        g_free (mb_key);
 
335
        g_free (mb_value);
 
336
        return ret;
 
337
}
 
338
#endif /* HAVE_NEW_LCMS */
 
339
 
 
340
static gboolean
 
341
gcm_apply_create_icc_profile_for_edid (GsdColorManager *manager,
 
342
                                       CdDevice *device,
 
343
                                       GcmEdid *edid,
 
344
                                       const gchar *filename,
 
345
                                       GError **error)
 
346
{
 
347
        const CdColorYxy *tmp;
 
348
        cmsCIExyYTRIPLE chroma;
 
349
        cmsCIExyY white_point;
 
350
        cmsHPROFILE lcms_profile = NULL;
 
351
        cmsToneCurve *transfer_curve[3] = { NULL, NULL, NULL };
 
352
        const gchar *data;
 
353
        gboolean ret = FALSE;
 
354
        gchar *str;
 
355
        gfloat edid_gamma;
 
356
        gfloat localgamma;
 
357
#ifdef HAVE_NEW_LCMS
 
358
        cmsHANDLE dict = NULL;
 
359
#endif
 
360
        GsdColorManagerPrivate *priv = manager->priv;
 
361
 
 
362
        /* ensure the per-user directory exists */
 
363
        ret = gcm_utils_mkdir_for_filename (filename, error);
 
364
        if (!ret)
 
365
                goto out;
 
366
 
 
367
        /* copy color data from our structures */
 
368
        tmp = gcm_edid_get_red (edid);
 
369
        chroma.Red.x = tmp->x;
 
370
        chroma.Red.y = tmp->y;
 
371
        tmp = gcm_edid_get_green (edid);
 
372
        chroma.Green.x = tmp->x;
 
373
        chroma.Green.y = tmp->y;
 
374
        tmp = gcm_edid_get_blue (edid);
 
375
        chroma.Blue.x = tmp->x;
 
376
        chroma.Blue.y = tmp->y;
 
377
        tmp = gcm_edid_get_white (edid);
 
378
        white_point.x = tmp->x;
 
379
        white_point.y = tmp->y;
 
380
        white_point.Y = 1.0;
 
381
 
 
382
        /* estimate the transfer function for the gamma */
 
383
        localgamma = gcm_edid_get_gamma (edid);
 
384
        transfer_curve[0] = transfer_curve[1] = transfer_curve[2] = cmsBuildGamma (NULL, localgamma);
 
385
 
 
386
        /* create our generated profile */
 
387
        lcms_profile = cmsCreateRGBProfile (&white_point, &chroma, transfer_curve);
 
388
        if (lcms_profile == NULL) {
 
389
                g_set_error (error,
 
390
                             GSD_COLOR_MANAGER_ERROR,
 
391
                             GSD_COLOR_MANAGER_ERROR_FAILED,
 
392
                             "failed to create profile");
 
393
                goto out;
 
394
        }
 
395
 
 
396
        cmsSetColorSpace (lcms_profile, cmsSigRgbData);
 
397
        cmsSetPCS (lcms_profile, cmsSigXYZData);
 
398
        cmsSetHeaderRenderingIntent (lcms_profile, INTENT_PERCEPTUAL);
 
399
        cmsSetDeviceClass (lcms_profile, cmsSigDisplayClass);
 
400
 
 
401
        /* copyright */
 
402
        ret = _cmsWriteTagTextAscii (lcms_profile,
 
403
                                     cmsSigCopyrightTag,
 
404
                                     /* deliberately not translated */
 
405
                                     "This profile is free of known copyright restrictions.");
 
406
        if (!ret) {
 
407
                g_set_error_literal (error,
 
408
                                     GSD_COLOR_MANAGER_ERROR,
 
409
                                     GSD_COLOR_MANAGER_ERROR_FAILED,
 
410
                                     "failed to write copyright");
 
411
                goto out;
 
412
        }
 
413
 
 
414
        /* set model */
 
415
        data = gcm_edid_get_monitor_name (edid);
 
416
        if (data == NULL)
 
417
                data = gcm_dmi_get_name (priv->dmi);
 
418
        if (data == NULL)
 
419
                data = "Unknown monitor";
 
420
        ret = _cmsWriteTagTextAscii (lcms_profile,
 
421
                                     cmsSigDeviceModelDescTag,
 
422
                                     data);
 
423
        if (!ret) {
 
424
                g_set_error_literal (error,
 
425
                                     GSD_COLOR_MANAGER_ERROR,
 
426
                                     GSD_COLOR_MANAGER_ERROR_FAILED,
 
427
                                     "failed to write model");
 
428
                goto out;
 
429
        }
 
430
 
 
431
        /* write title */
 
432
        ret = _cmsWriteTagTextAscii (lcms_profile,
 
433
                                     cmsSigProfileDescriptionTag,
 
434
                                     data);
 
435
        if (!ret) {
 
436
                g_set_error_literal (error, GSD_COLOR_MANAGER_ERROR, GSD_COLOR_MANAGER_ERROR_FAILED, "failed to write description");
 
437
                goto out;
 
438
        }
 
439
 
 
440
        /* get manufacturer */
 
441
        data = gcm_edid_get_vendor_name (edid);
 
442
        if (data == NULL)
 
443
                data = gcm_dmi_get_vendor (priv->dmi);
 
444
        if (data == NULL)
 
445
                data = "Unknown vendor";
 
446
        ret = _cmsWriteTagTextAscii (lcms_profile,
 
447
                                     cmsSigDeviceMfgDescTag,
 
448
                                     data);
 
449
        if (!ret) {
 
450
                g_set_error_literal (error,
 
451
                                     GSD_COLOR_MANAGER_ERROR,
 
452
                                     GSD_COLOR_MANAGER_ERROR_FAILED,
 
453
                                     "failed to write manufacturer");
 
454
                goto out;
 
455
        }
 
456
 
 
457
#ifdef HAVE_NEW_LCMS
 
458
        /* just create a new dict */
 
459
        dict = cmsDictAlloc (NULL);
 
460
 
 
461
        /* set the framework creator metadata */
 
462
        _cmsDictAddEntryAscii (dict,
 
463
                               CD_PROFILE_METADATA_CMF_PRODUCT,
 
464
                               PACKAGE_NAME);
 
465
        _cmsDictAddEntryAscii (dict,
 
466
                               CD_PROFILE_METADATA_CMF_BINARY,
 
467
                               PACKAGE_NAME);
 
468
        _cmsDictAddEntryAscii (dict,
 
469
                               CD_PROFILE_METADATA_CMF_VERSION,
 
470
                               PACKAGE_VERSION);
 
471
        _cmsDictAddEntryAscii (dict,
 
472
                               CD_PROFILE_METADATA_MAPPING_DEVICE_ID,
 
473
                               cd_device_get_id (device));
 
474
 
 
475
        /* set the data source so we don't ever prompt the user to
 
476
         * recalibrate (as the EDID data won't have changed) */
 
477
        _cmsDictAddEntryAscii (dict,
 
478
                               CD_PROFILE_METADATA_DATA_SOURCE,
 
479
                               CD_PROFILE_METADATA_DATA_SOURCE_EDID);
 
480
 
 
481
        /* set 'ICC meta Tag for Monitor Profiles' data */
 
482
        _cmsDictAddEntryAscii (dict, "EDID_md5", gcm_edid_get_checksum (edid));
 
483
        data = gcm_edid_get_monitor_name (edid);
 
484
        if (data != NULL)
 
485
                _cmsDictAddEntryAscii (dict, "EDID_model", data);
 
486
        data = gcm_edid_get_serial_number (edid);
 
487
        if (data != NULL)
 
488
                _cmsDictAddEntryAscii (dict, "EDID_serial", data);
 
489
        data = gcm_edid_get_pnp_id (edid);
 
490
        if (data != NULL)
 
491
                _cmsDictAddEntryAscii (dict, "EDID_mnft", data);
 
492
        data = gcm_edid_get_vendor_name (edid);
 
493
        if (data != NULL)
 
494
                _cmsDictAddEntryAscii (dict, "EDID_manufacturer", data);
 
495
        edid_gamma = gcm_edid_get_gamma (edid);
 
496
        if (edid_gamma > 0.0 && edid_gamma < 10.0) {
 
497
                str = g_strdup_printf ("%f", edid_gamma);
 
498
                _cmsDictAddEntryAscii (dict, "EDID_gamma", str);
 
499
                g_free (str);
 
500
        }
 
501
 
 
502
        /* also add the primaries */
 
503
        str = g_strdup_printf ("%f", chroma.Red.x);
 
504
        _cmsDictAddEntryAscii (dict, "EDID_red_x", str);
 
505
        g_free (str);
 
506
        str = g_strdup_printf ("%f", chroma.Red.y);
 
507
        _cmsDictAddEntryAscii (dict, "EDID_red_y", str);
 
508
        g_free (str);
 
509
        str = g_strdup_printf ("%f", chroma.Green.x);
 
510
        _cmsDictAddEntryAscii (dict, "EDID_green_x", str);
 
511
        g_free (str);
 
512
        str = g_strdup_printf ("%f", chroma.Green.y);
 
513
        _cmsDictAddEntryAscii (dict, "EDID_green_y", str);
 
514
        g_free (str);
 
515
        str = g_strdup_printf ("%f", chroma.Blue.x);
 
516
        _cmsDictAddEntryAscii (dict, "EDID_blue_x", str);
 
517
        g_free (str);
 
518
        str = g_strdup_printf ("%f", chroma.Blue.y);
 
519
        _cmsDictAddEntryAscii (dict, "EDID_blue_y", str);
 
520
        g_free (str);
 
521
 
 
522
        /* write new tag */
 
523
        ret = cmsWriteTag (lcms_profile, cmsSigMetaTag, dict);
 
524
        if (!ret) {
 
525
                g_set_error_literal (error,
 
526
                                     GSD_COLOR_MANAGER_ERROR,
 
527
                                     GSD_COLOR_MANAGER_ERROR_FAILED,
 
528
                                     "failed to write profile metadata");
 
529
                goto out;
 
530
        }
 
531
#endif /* HAVE_NEW_LCMS */
 
532
 
 
533
        /* write profile id */
 
534
        ret = cmsMD5computeID (lcms_profile);
 
535
        if (!ret) {
 
536
                g_set_error_literal (error,
 
537
                                     GSD_COLOR_MANAGER_ERROR,
 
538
                                     GSD_COLOR_MANAGER_ERROR_FAILED,
 
539
                                     "failed to write profile id");
 
540
                goto out;
 
541
        }
 
542
 
 
543
        /* save, TODO: get error */
 
544
        cmsSaveProfileToFile (lcms_profile, filename);
 
545
        ret = TRUE;
 
546
out:
 
547
#ifdef HAVE_NEW_LCMS
 
548
        if (dict != NULL)
 
549
                cmsDictFree (dict);
 
550
#endif
 
551
        if (*transfer_curve != NULL)
 
552
                cmsFreeToneCurve (*transfer_curve);
 
553
        return ret;
 
554
}
 
555
 
 
556
static GPtrArray *
 
557
gcm_session_generate_vcgt (CdProfile *profile, guint size)
 
558
{
 
559
        GnomeRROutputClutItem *tmp;
 
560
        GPtrArray *array = NULL;
 
561
        const cmsToneCurve **vcgt;
 
562
        cmsFloat32Number in;
 
563
        guint i;
 
564
        const gchar *filename;
 
565
        cmsHPROFILE lcms_profile = NULL;
 
566
 
 
567
        /* invalid size */
 
568
        if (size == 0)
 
569
                goto out;
 
570
 
 
571
        /* not an actual profile */
 
572
        filename = cd_profile_get_filename (profile);
 
573
        if (filename == NULL)
 
574
                goto out;
 
575
 
 
576
        /* open file */
 
577
        lcms_profile = cmsOpenProfileFromFile (filename, "r");
 
578
        if (lcms_profile == NULL)
 
579
                goto out;
 
580
 
 
581
        /* get tone curves from profile */
 
582
        vcgt = cmsReadTag (lcms_profile, cmsSigVcgtTag);
 
583
        if (vcgt == NULL || vcgt[0] == NULL) {
 
584
                g_debug ("profile does not have any VCGT data");
 
585
                goto out;
 
586
        }
 
587
 
 
588
        /* create array */
 
589
        array = g_ptr_array_new_with_free_func (g_free);
 
590
        for (i = 0; i < size; i++) {
 
591
                in = (gdouble) i / (gdouble) (size - 1);
 
592
                tmp = g_new0 (GnomeRROutputClutItem, 1);
 
593
                tmp->red = cmsEvalToneCurveFloat(vcgt[0], in) * (gdouble) 0xffff;
 
594
                tmp->green = cmsEvalToneCurveFloat(vcgt[1], in) * (gdouble) 0xffff;
 
595
                tmp->blue = cmsEvalToneCurveFloat(vcgt[2], in) * (gdouble) 0xffff;
 
596
                g_ptr_array_add (array, tmp);
 
597
        }
 
598
out:
 
599
        if (lcms_profile != NULL)
 
600
                cmsCloseProfile (lcms_profile);
 
601
        return array;
 
602
}
 
603
 
 
604
static guint
 
605
gnome_rr_output_get_gamma_size (GnomeRROutput *output)
 
606
{
 
607
        GnomeRRCrtc *crtc;
 
608
        gint len = 0;
 
609
 
 
610
        crtc = gnome_rr_output_get_crtc (output);
 
611
        if (crtc == NULL)
 
612
                return 0;
 
613
        gnome_rr_crtc_get_gamma (crtc,
 
614
                                 &len,
 
615
                                 NULL, NULL, NULL);
 
616
        return (guint) len;
 
617
}
 
618
 
 
619
static gboolean
 
620
gcm_session_output_set_gamma (GnomeRROutput *output,
 
621
                              GPtrArray *array,
 
622
                              GError **error)
 
623
{
 
624
        gboolean ret = TRUE;
 
625
        guint16 *red = NULL;
 
626
        guint16 *green = NULL;
 
627
        guint16 *blue = NULL;
 
628
        guint i;
 
629
        GnomeRROutputClutItem *data;
 
630
        GnomeRRCrtc *crtc;
 
631
 
 
632
        /* no length? */
 
633
        if (array->len == 0) {
 
634
                ret = FALSE;
 
635
                g_set_error_literal (error,
 
636
                                     GSD_COLOR_MANAGER_ERROR,
 
637
                                     GSD_COLOR_MANAGER_ERROR_FAILED,
 
638
                                     "no data in the CLUT array");
 
639
                goto out;
 
640
        }
 
641
 
 
642
        /* convert to a type X understands */
 
643
        red = g_new (guint16, array->len);
 
644
        green = g_new (guint16, array->len);
 
645
        blue = g_new (guint16, array->len);
 
646
        for (i = 0; i < array->len; i++) {
 
647
                data = g_ptr_array_index (array, i);
 
648
                red[i] = data->red;
 
649
                green[i] = data->green;
 
650
                blue[i] = data->blue;
 
651
        }
 
652
 
 
653
        /* send to LUT */
 
654
        crtc = gnome_rr_output_get_crtc (output);
 
655
        if (crtc == NULL) {
 
656
                ret = FALSE;
 
657
                g_set_error (error,
 
658
                             GSD_COLOR_MANAGER_ERROR,
 
659
                             GSD_COLOR_MANAGER_ERROR_FAILED,
 
660
                             "failed to get ctrc for %s",
 
661
                             gnome_rr_output_get_name (output));
 
662
                goto out;
 
663
        }
 
664
        gnome_rr_crtc_set_gamma (crtc, array->len,
 
665
                                 red, green, blue);
 
666
out:
 
667
        g_free (red);
 
668
        g_free (green);
 
669
        g_free (blue);
 
670
        return ret;
 
671
}
 
672
 
 
673
static gboolean
 
674
gcm_session_device_set_gamma (GnomeRROutput *output,
 
675
                              CdProfile *profile,
 
676
                              GError **error)
 
677
{
 
678
        gboolean ret = FALSE;
 
679
        guint size;
 
680
        GPtrArray *clut = NULL;
 
681
 
 
682
        /* create a lookup table */
 
683
        size = gnome_rr_output_get_gamma_size (output);
 
684
        if (size == 0) {
 
685
                ret = TRUE;
 
686
                goto out;
 
687
        }
 
688
        clut = gcm_session_generate_vcgt (profile, size);
 
689
        if (clut == NULL) {
 
690
                g_set_error_literal (error,
 
691
                                     GSD_COLOR_MANAGER_ERROR,
 
692
                                     GSD_COLOR_MANAGER_ERROR_FAILED,
 
693
                                     "failed to generate vcgt");
 
694
                goto out;
 
695
        }
 
696
 
 
697
        /* apply the vcgt to this output */
 
698
        ret = gcm_session_output_set_gamma (output, clut, error);
 
699
        if (!ret)
 
700
                goto out;
 
701
out:
 
702
        if (clut != NULL)
 
703
                g_ptr_array_unref (clut);
 
704
        return ret;
 
705
}
 
706
 
 
707
static gboolean
 
708
gcm_session_device_reset_gamma (GnomeRROutput *output,
 
709
                                GError **error)
 
710
{
 
711
        gboolean ret;
 
712
        guint i;
 
713
        guint size;
 
714
        guint32 value;
 
715
        GPtrArray *clut;
 
716
        GnomeRROutputClutItem *data;
 
717
 
 
718
        /* create a linear ramp */
 
719
        g_debug ("falling back to dummy ramp");
 
720
        clut = g_ptr_array_new_with_free_func (g_free);
 
721
        size = gnome_rr_output_get_gamma_size (output);
 
722
        if (size == 0) {
 
723
                ret = TRUE;
 
724
                goto out;
 
725
        }
 
726
        for (i = 0; i < size; i++) {
 
727
                value = (i * 0xffff) / (size - 1);
 
728
                data = g_new0 (GnomeRROutputClutItem, 1);
 
729
                data->red = value;
 
730
                data->green = value;
 
731
                data->blue = value;
 
732
                g_ptr_array_add (clut, data);
 
733
        }
 
734
 
 
735
        /* apply the vcgt to this output */
 
736
        ret = gcm_session_output_set_gamma (output, clut, error);
 
737
        if (!ret)
 
738
                goto out;
 
739
out:
 
740
        g_ptr_array_unref (clut);
 
741
        return ret;
 
742
}
 
743
 
 
744
static GnomeRROutput *
 
745
gcm_session_get_x11_output_by_id (GsdColorManager *manager,
 
746
                                  const gchar *device_id,
 
747
                                  GError **error)
 
748
{
 
749
        gchar *output_id;
 
750
        GnomeRROutput *output = NULL;
 
751
        GnomeRROutput **outputs = NULL;
 
752
        guint i;
 
753
        GsdColorManagerPrivate *priv = manager->priv;
 
754
 
 
755
        /* search all X11 outputs for the device id */
 
756
        outputs = gnome_rr_screen_list_outputs (priv->x11_screen);
 
757
        if (outputs == NULL) {
 
758
                g_set_error_literal (error,
 
759
                                     GSD_COLOR_MANAGER_ERROR,
 
760
                                     GSD_COLOR_MANAGER_ERROR_FAILED,
 
761
                                     "Failed to get outputs");
 
762
                goto out;
 
763
        }
 
764
        for (i = 0; outputs[i] != NULL && output == NULL; i++) {
 
765
                if (!gnome_rr_output_is_connected (outputs[i]))
 
766
                        continue;
 
767
                output_id = gcm_session_get_output_id (manager, outputs[i]);
 
768
                if (g_strcmp0 (output_id, device_id) == 0)
 
769
                        output = outputs[i];
 
770
                g_free (output_id);
 
771
        }
 
772
        if (output == NULL) {
 
773
                g_set_error (error,
 
774
                             GSD_COLOR_MANAGER_ERROR,
 
775
                             GSD_COLOR_MANAGER_ERROR_FAILED,
 
776
                             "Failed to find output %s",
 
777
                             device_id);
 
778
        }
 
779
out:
 
780
        return output;
 
781
}
 
782
 
 
783
/* this function is more complicated than it should be, due to the
 
784
 * fact that XOrg sometimes assigns no primary devices when using
 
785
 * "xrandr --auto" or when the version of RANDR is < 1.3 */
 
786
static gboolean
 
787
gcm_session_use_output_profile_for_screen (GsdColorManager *manager,
 
788
                                           GnomeRROutput *output)
 
789
{
 
790
        gboolean has_laptop = FALSE;
 
791
        gboolean has_primary = FALSE;
 
792
        GnomeRROutput **outputs;
 
793
        GnomeRROutput *connected = NULL;
 
794
        guint i;
 
795
 
 
796
        /* do we have any screens marked as primary */
 
797
        outputs = gnome_rr_screen_list_outputs (manager->priv->x11_screen);
 
798
        if (outputs == NULL || outputs[0] == NULL) {
 
799
                g_warning ("failed to get outputs");
 
800
                return FALSE;
 
801
        }
 
802
        for (i = 0; outputs[i] != NULL; i++) {
 
803
                if (!gnome_rr_output_is_connected (outputs[i]))
 
804
                        continue;
 
805
                if (connected == NULL)
 
806
                        connected = outputs[i];
 
807
                if (gnome_rr_output_get_is_primary (outputs[i]))
 
808
                        has_primary = TRUE;
 
809
                if (gnome_rr_output_is_laptop (outputs[i]))
 
810
                        has_laptop = TRUE;
 
811
        }
 
812
 
 
813
        /* we have an assigned primary device, are we that? */
 
814
        if (has_primary)
 
815
                return gnome_rr_output_get_is_primary (output);
 
816
 
 
817
        /* choosing the internal panel is probably sane */
 
818
        if (has_laptop)
 
819
                return gnome_rr_output_is_laptop (output);
 
820
 
 
821
        /* we have to choose one, so go for the first connected device */
 
822
        if (connected != NULL)
 
823
                return gnome_rr_output_get_id (connected) == gnome_rr_output_get_id (output);
 
824
 
 
825
        return FALSE;
 
826
}
 
827
 
 
828
/* TODO: remove when we can dep on a released version of colord */
 
829
#ifndef CD_PROFILE_METADATA_SCREEN_BRIGHTNESS
 
830
#define CD_PROFILE_METADATA_SCREEN_BRIGHTNESS           "SCREEN_brightness"
 
831
#endif
 
832
 
 
833
#define GSD_DBUS_NAME_POWER             GSD_DBUS_NAME ".Power"
 
834
#define GSD_DBUS_INTERFACE_POWER_SCREEN GSD_DBUS_BASE_INTERFACE ".Power.Screen"
 
835
#define GSD_DBUS_PATH_POWER             GSD_DBUS_PATH "/Power"
 
836
 
 
837
static void
 
838
gcm_session_set_output_percentage_cb (GObject *source_object,
 
839
                                      GAsyncResult *res,
 
840
                                      gpointer user_data)
 
841
{
 
842
        GDBusConnection *connection = G_DBUS_CONNECTION (source_object);
 
843
        GError *error = NULL;
 
844
        GVariant *retval;
 
845
        retval = g_dbus_connection_call_finish (connection,
 
846
                                                res,
 
847
                                                &error);
 
848
        if (retval == NULL) {
 
849
                g_warning ("failed to set output brightness: %s",
 
850
                           error->message);
 
851
                g_error_free (error);
 
852
                return;
 
853
        }
 
854
        g_variant_unref (retval);
 
855
}
 
856
 
 
857
static void
 
858
gcm_session_set_output_percentage (guint percentage)
 
859
{
 
860
        GDBusConnection *connection;
 
861
 
 
862
        /* get a ref to the existing bus connection */
 
863
        connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
 
864
        if (connection == NULL)
 
865
                return;
 
866
        g_dbus_connection_call (connection,
 
867
                                GSD_DBUS_NAME_POWER,
 
868
                                GSD_DBUS_PATH_POWER,
 
869
                                GSD_DBUS_INTERFACE_POWER_SCREEN,
 
870
                                "SetPercentage",
 
871
                                g_variant_new ("(u)", percentage),
 
872
                                NULL,
 
873
                                G_DBUS_CALL_FLAGS_NONE,
 
874
                                -1, NULL,
 
875
                                gcm_session_set_output_percentage_cb, NULL);
 
876
        g_object_unref (connection);
 
877
}
 
878
 
 
879
static void
 
880
gcm_session_device_assign_profile_connect_cb (GObject *object,
 
881
                                              GAsyncResult *res,
 
882
                                              gpointer user_data)
 
883
{
 
884
        CdProfile *profile = CD_PROFILE (object);
 
885
        const gchar *brightness_profile;
 
886
        const gchar *filename;
 
887
        gboolean ret;
 
888
        GError *error = NULL;
 
889
        GnomeRROutput *output;
 
890
        guint brightness_percentage;
 
891
        GcmSessionAsyncHelper *helper = (GcmSessionAsyncHelper *) user_data;
 
892
        GsdColorManager *manager = GSD_COLOR_MANAGER (helper->manager);
 
893
 
 
894
        /* get properties */
 
895
        ret = cd_profile_connect_finish (profile, res, &error);
 
896
        if (!ret) {
 
897
                g_warning ("failed to connect to profile: %s",
 
898
                           error->message);
 
899
                g_error_free (error);
 
900
                goto out;
 
901
        }
 
902
 
 
903
        /* get the filename */
 
904
        filename = cd_profile_get_filename (profile);
 
905
        g_assert (filename != NULL);
 
906
 
 
907
        /* get the output (can't save in helper as GnomeRROutput isn't
 
908
         * a GObject, just a pointer */
 
909
        output = gnome_rr_screen_get_output_by_id (manager->priv->x11_screen,
 
910
                                                   helper->output_id);
 
911
        if (output == NULL)
 
912
                goto out;
 
913
 
 
914
        /* if output is a laptop screen and the profile has a
 
915
         * calibration brightness then set this new brightness */
 
916
        brightness_profile = cd_profile_get_metadata_item (profile,
 
917
                                                           CD_PROFILE_METADATA_SCREEN_BRIGHTNESS);
 
918
        if (gnome_rr_output_is_laptop (output) &&
 
919
            brightness_profile != NULL) {
 
920
                /* the percentage is stored in the profile metadata as
 
921
                 * a string, not ideal, but it's all we have... */
 
922
                brightness_percentage = atoi (brightness_profile);
 
923
                gcm_session_set_output_percentage (brightness_percentage);
 
924
        }
 
925
 
 
926
        /* set the _ICC_PROFILE atom */
 
927
        ret = gcm_session_use_output_profile_for_screen (manager, output);
 
928
        if (ret) {
 
929
                ret = gcm_session_screen_set_icc_profile (manager,
 
930
                                                          filename,
 
931
                                                          &error);
 
932
                if (!ret) {
 
933
                        g_warning ("failed to set screen _ICC_PROFILE: %s",
 
934
                                   error->message);
 
935
                        g_clear_error (&error);
 
936
                }
 
937
        }
 
938
 
 
939
        /* create a vcgt for this icc file */
 
940
        ret = cd_profile_get_has_vcgt (profile);
 
941
        if (ret) {
 
942
                ret = gcm_session_device_set_gamma (output,
 
943
                                                    profile,
 
944
                                                    &error);
 
945
                if (!ret) {
 
946
                        g_warning ("failed to set %s gamma tables: %s",
 
947
                                   cd_device_get_id (helper->device),
 
948
                                   error->message);
 
949
                        g_error_free (error);
 
950
                        goto out;
 
951
                }
 
952
        } else {
 
953
                ret = gcm_session_device_reset_gamma (output,
 
954
                                                      &error);
 
955
                if (!ret) {
 
956
                        g_warning ("failed to reset %s gamma tables: %s",
 
957
                                   cd_device_get_id (helper->device),
 
958
                                   error->message);
 
959
                        g_error_free (error);
 
960
                        goto out;
 
961
                }
 
962
        }
 
963
out:
 
964
        gcm_session_async_helper_free (helper);
 
965
}
 
966
 
 
967
/*
 
968
 * Check to see if the on-disk profile has the MAPPING_device_id
 
969
 * metadata, and if not, we should delete the profile and re-create it
 
970
 * so that it gets mapped by the daemon.
 
971
 */
 
972
static gboolean
 
973
gcm_session_check_profile_device_md (const gchar *filename)
 
974
{
 
975
        cmsHANDLE dict;
 
976
        cmsHPROFILE lcms_profile;
 
977
        const cmsDICTentry *entry;
 
978
        gboolean ret = FALSE;
 
979
        gchar ascii_name[1024];
 
980
        gsize len;
 
981
 
 
982
        /* parse the ICC file */
 
983
        lcms_profile = cmsOpenProfileFromFile (filename, "r");
 
984
        if (lcms_profile == NULL)
 
985
                goto out;
 
986
 
 
987
        /* does profile have metadata? */
 
988
        dict = cmsReadTag (lcms_profile, cmsSigMetaTag);
 
989
        if (dict == NULL) {
 
990
                g_debug ("auto-edid profile is old, and contains no metadata");
 
991
                goto out;
 
992
        }
 
993
        for (entry = cmsDictGetEntryList (dict);
 
994
             entry != NULL;
 
995
             entry = cmsDictNextEntry (entry)) {
 
996
                if (entry->Name == NULL)
 
997
                        continue;
 
998
                len = wcstombs (ascii_name, entry->Name, sizeof (ascii_name));
 
999
                if (len == (gsize) -1)
 
1000
                        continue;
 
1001
                if (g_strcmp0 (ascii_name,
 
1002
                               CD_PROFILE_METADATA_MAPPING_DEVICE_ID) == 0) {
 
1003
                        ret = TRUE;
 
1004
                        goto out;
 
1005
                }
 
1006
        }
 
1007
        g_debug ("auto-edid profile is old, and contains no %s data",
 
1008
                 CD_PROFILE_METADATA_MAPPING_DEVICE_ID);
 
1009
out:
 
1010
        if (lcms_profile != NULL)
 
1011
                cmsCloseProfile (lcms_profile);
 
1012
        return ret;
 
1013
}
 
1014
 
 
1015
static void
 
1016
gcm_session_device_assign_connect_cb (GObject *object,
 
1017
                                      GAsyncResult *res,
 
1018
                                      gpointer user_data)
 
1019
{
 
1020
        CdDeviceKind kind;
 
1021
        CdProfile *profile = NULL;
 
1022
        gboolean ret;
 
1023
        gchar *autogen_filename = NULL;
 
1024
        gchar *autogen_path = NULL;
 
1025
        GcmEdid *edid = NULL;
 
1026
        GnomeRROutput *output = NULL;
 
1027
        GError *error = NULL;
 
1028
        const gchar *xrandr_id;
 
1029
        GcmSessionAsyncHelper *helper;
 
1030
        CdDevice *device = CD_DEVICE (object);
 
1031
        GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
 
1032
        GsdColorManagerPrivate *priv = manager->priv;
 
1033
 
 
1034
        /* remove from assign array */
 
1035
        g_hash_table_remove (manager->priv->device_assign_hash,
 
1036
                             cd_device_get_object_path (device));
 
1037
 
 
1038
        /* get properties */
 
1039
        ret = cd_device_connect_finish (device, res, &error);
 
1040
        if (!ret) {
 
1041
                g_warning ("failed to connect to device: %s",
 
1042
                           error->message);
 
1043
                g_error_free (error);
 
1044
                goto out;
 
1045
        }
 
1046
 
 
1047
        /* check we care */
 
1048
        kind = cd_device_get_kind (device);
 
1049
        if (kind != CD_DEVICE_KIND_DISPLAY)
 
1050
                goto out;
 
1051
 
 
1052
        g_debug ("need to assign display device %s",
 
1053
                 cd_device_get_id (device));
 
1054
 
 
1055
        /* get the GnomeRROutput for the device id */
 
1056
        xrandr_id = cd_device_get_id (device);
 
1057
        output = gcm_session_get_x11_output_by_id (manager,
 
1058
                                                   xrandr_id,
 
1059
                                                   &error);
 
1060
        if (output == NULL) {
 
1061
                g_warning ("no %s device found: %s",
 
1062
                           cd_device_get_id (device),
 
1063
                           error->message);
 
1064
                g_error_free (error);
 
1065
                goto out;
 
1066
        }
 
1067
 
 
1068
        /* create profile from device edid if it exists */
 
1069
        edid = gcm_session_get_output_edid (manager, output, &error);
 
1070
        if (edid == NULL) {
 
1071
                g_warning ("unable to get EDID for %s: %s",
 
1072
                           cd_device_get_id (device),
 
1073
                           error->message);
 
1074
                g_clear_error (&error);
 
1075
 
 
1076
        } else {
 
1077
                autogen_filename = g_strdup_printf ("edid-%s.icc",
 
1078
                                                    gcm_edid_get_checksum (edid));
 
1079
                autogen_path = g_build_filename (g_get_user_data_dir (),
 
1080
                                                 "icc", autogen_filename, NULL);
 
1081
 
 
1082
                /* check if auto-profile has up-to-date metadata */
 
1083
                if (gcm_session_check_profile_device_md (autogen_path)) {
 
1084
                        g_debug ("auto-profile edid %s exists with md", autogen_path);
 
1085
                } else {
 
1086
                        g_debug ("auto-profile edid does not exist, creating as %s",
 
1087
                                 autogen_path);
 
1088
                        ret = gcm_apply_create_icc_profile_for_edid (manager,
 
1089
                                                                     device,
 
1090
                                                                     edid,
 
1091
                                                                     autogen_path,
 
1092
                                                                     &error);
 
1093
                        if (!ret) {
 
1094
                                g_warning ("failed to create profile from EDID data: %s",
 
1095
                                             error->message);
 
1096
                                g_clear_error (&error);
 
1097
                        }
 
1098
                }
 
1099
        }
 
1100
 
 
1101
        /* get the default profile for the device */
 
1102
        profile = cd_device_get_default_profile (device);
 
1103
        if (profile == NULL) {
 
1104
                g_debug ("%s has no default profile to set",
 
1105
                         cd_device_get_id (device));
 
1106
 
 
1107
                /* the default output? */
 
1108
                if (gnome_rr_output_get_is_primary (output)) {
 
1109
                        gdk_property_delete (priv->gdk_window,
 
1110
                                             gdk_atom_intern_static_string ("_ICC_PROFILE"));
 
1111
                        gdk_property_delete (priv->gdk_window,
 
1112
                                             gdk_atom_intern_static_string ("_ICC_PROFILE_IN_X_VERSION"));
 
1113
                }
 
1114
 
 
1115
                /* reset, as we want linear profiles for profiling */
 
1116
                ret = gcm_session_device_reset_gamma (output,
 
1117
                                                      &error);
 
1118
                if (!ret) {
 
1119
                        g_warning ("failed to reset %s gamma tables: %s",
 
1120
                                   cd_device_get_id (device),
 
1121
                                   error->message);
 
1122
                        g_error_free (error);
 
1123
                        goto out;
 
1124
                }
 
1125
                goto out;
 
1126
        }
 
1127
 
 
1128
        /* get properties */
 
1129
        helper = g_new0 (GcmSessionAsyncHelper, 1);
 
1130
        helper->output_id = gnome_rr_output_get_id (output);
 
1131
        helper->manager = g_object_ref (manager);
 
1132
        helper->device = g_object_ref (device);
 
1133
        cd_profile_connect (profile,
 
1134
                            NULL,
 
1135
                            gcm_session_device_assign_profile_connect_cb,
 
1136
                            helper);
 
1137
out:
 
1138
        g_free (autogen_filename);
 
1139
        g_free (autogen_path);
 
1140
        if (edid != NULL)
 
1141
                g_object_unref (edid);
 
1142
        if (profile != NULL)
 
1143
                g_object_unref (profile);
 
1144
}
 
1145
 
 
1146
static void
 
1147
gcm_session_device_assign (GsdColorManager *manager, CdDevice *device)
 
1148
{
 
1149
        const gchar *key;
 
1150
        gpointer found;
 
1151
 
 
1152
        /* are we already assigning this device */
 
1153
        key = cd_device_get_object_path (device);
 
1154
        found = g_hash_table_lookup (manager->priv->device_assign_hash, key);
 
1155
        if (found != NULL) {
 
1156
                g_debug ("assign for %s already in progress", key);
 
1157
                return;
 
1158
        }
 
1159
        g_hash_table_insert (manager->priv->device_assign_hash,
 
1160
                             g_strdup (key),
 
1161
                             GINT_TO_POINTER (TRUE));
 
1162
        cd_device_connect (device,
 
1163
                           NULL,
 
1164
                           gcm_session_device_assign_connect_cb,
 
1165
                           manager);
 
1166
}
 
1167
 
 
1168
static void
 
1169
gcm_session_device_added_assign_cb (CdClient *client,
 
1170
                                    CdDevice *device,
 
1171
                                    GsdColorManager *manager)
 
1172
{
 
1173
        gcm_session_device_assign (manager, device);
 
1174
}
 
1175
 
 
1176
static void
 
1177
gcm_session_device_changed_assign_cb (CdClient *client,
 
1178
                                      CdDevice *device,
 
1179
                                      GsdColorManager *manager)
 
1180
{
 
1181
        g_debug ("%s changed", cd_device_get_object_path (device));
 
1182
        gcm_session_device_assign (manager, device);
 
1183
}
 
1184
 
 
1185
static void
 
1186
gcm_session_create_device_cb (GObject *object,
 
1187
                              GAsyncResult *res,
 
1188
                              gpointer user_data)
 
1189
{
 
1190
        CdDevice *device;
 
1191
        GError *error = NULL;
 
1192
 
 
1193
        device = cd_client_create_device_finish (CD_CLIENT (object),
 
1194
                                                 res,
 
1195
                                                 &error);
 
1196
        if (device == NULL) {
 
1197
                if (error->domain != CD_CLIENT_ERROR ||
 
1198
                    error->code != CD_CLIENT_ERROR_ALREADY_EXISTS) {
 
1199
                        g_warning ("failed to create device: %s",
 
1200
                                   error->message);
 
1201
                }
 
1202
                g_error_free (error);
 
1203
                return;
 
1204
        }
 
1205
        g_object_unref (device);
 
1206
}
 
1207
 
 
1208
static void
 
1209
gcm_session_add_x11_output (GsdColorManager *manager, GnomeRROutput *output)
 
1210
{
 
1211
        const gchar *edid_checksum = NULL;
 
1212
        const gchar *model = NULL;
 
1213
        const gchar *serial = NULL;
 
1214
        const gchar *vendor = NULL;
 
1215
        gboolean ret;
 
1216
        gchar *device_id = NULL;
 
1217
        GcmEdid *edid;
 
1218
        GError *error = NULL;
 
1219
        GHashTable *device_props = NULL;
 
1220
        GsdColorManagerPrivate *priv = manager->priv;
 
1221
 
 
1222
        /* try to get edid */
 
1223
        edid = gcm_session_get_output_edid (manager, output, &error);
 
1224
        if (edid == NULL) {
 
1225
                g_warning ("failed to get edid: %s",
 
1226
                           error->message);
 
1227
                g_clear_error (&error);
 
1228
        }
 
1229
 
 
1230
        /* prefer DMI data for the internal output */
 
1231
        ret = gnome_rr_output_is_laptop (output);
 
1232
        if (ret) {
 
1233
                model = gcm_dmi_get_name (priv->dmi);
 
1234
                vendor = gcm_dmi_get_vendor (priv->dmi);
 
1235
        }
 
1236
 
 
1237
        /* use EDID data if we have it */
 
1238
        if (edid != NULL) {
 
1239
                edid_checksum = gcm_edid_get_checksum (edid);
 
1240
                if (model == NULL)
 
1241
                        model = gcm_edid_get_monitor_name (edid);
 
1242
                if (vendor == NULL)
 
1243
                        vendor = gcm_edid_get_vendor_name (edid);
 
1244
                if (serial == NULL)
 
1245
                        serial = gcm_edid_get_serial_number (edid);
 
1246
        }
 
1247
 
 
1248
        /* ensure mandatory fields are set */
 
1249
        if (model == NULL)
 
1250
                model = gnome_rr_output_get_name (output);
 
1251
        if (vendor == NULL)
 
1252
                vendor = "unknown";
 
1253
        if (serial == NULL)
 
1254
                serial = "unknown";
 
1255
 
 
1256
        device_id = gcm_session_get_output_id (manager, output);
 
1257
        g_debug ("output %s added", device_id);
 
1258
        device_props = g_hash_table_new_full (g_str_hash, g_str_equal,
 
1259
                                              NULL, NULL);
 
1260
        g_hash_table_insert (device_props,
 
1261
                             (gpointer) CD_DEVICE_PROPERTY_KIND,
 
1262
                             (gpointer) cd_device_kind_to_string (CD_DEVICE_KIND_DISPLAY));
 
1263
        g_hash_table_insert (device_props,
 
1264
                             (gpointer) CD_DEVICE_PROPERTY_MODE,
 
1265
                             (gpointer) cd_device_mode_to_string (CD_DEVICE_MODE_PHYSICAL));
 
1266
        g_hash_table_insert (device_props,
 
1267
                             (gpointer) CD_DEVICE_PROPERTY_COLORSPACE,
 
1268
                             (gpointer) cd_colorspace_to_string (CD_COLORSPACE_RGB));
 
1269
        g_hash_table_insert (device_props,
 
1270
                             (gpointer) CD_DEVICE_PROPERTY_VENDOR,
 
1271
                             (gpointer) vendor);
 
1272
        g_hash_table_insert (device_props,
 
1273
                             (gpointer) CD_DEVICE_PROPERTY_MODEL,
 
1274
                             (gpointer) model);
 
1275
        g_hash_table_insert (device_props,
 
1276
                             (gpointer) CD_DEVICE_PROPERTY_SERIAL,
 
1277
                             (gpointer) serial);
 
1278
        g_hash_table_insert (device_props,
 
1279
                             (gpointer) CD_DEVICE_METADATA_XRANDR_NAME,
 
1280
                             (gpointer) gnome_rr_output_get_name (output));
 
1281
#if CD_CHECK_VERSION(0,1,25)
 
1282
        g_hash_table_insert (device_props,
 
1283
                             (gpointer) CD_DEVICE_METADATA_OUTPUT_PRIORITY,
 
1284
                             gnome_rr_output_get_is_primary (output) ?
 
1285
                             (gpointer) CD_DEVICE_METADATA_OUTPUT_PRIORITY_PRIMARY :
 
1286
                             (gpointer) CD_DEVICE_METADATA_OUTPUT_PRIORITY_SECONDARY);
 
1287
#endif
 
1288
#if CD_CHECK_VERSION(0,1,34)
 
1289
        if (edid_checksum != NULL) {
 
1290
                g_hash_table_insert (device_props,
 
1291
                                     (gpointer) CD_DEVICE_METADATA_OUTPUT_EDID_MD5,
 
1292
                                     (gpointer) edid_checksum);
 
1293
        }
 
1294
#endif
 
1295
#if CD_CHECK_VERSION(0,1,27)
 
1296
        /* set this so we can call the device a 'Laptop Screen' in the
 
1297
         * control center main panel */
 
1298
        if (gnome_rr_output_is_laptop (output)) {
 
1299
                g_hash_table_insert (device_props,
 
1300
                                     (gpointer) CD_DEVICE_PROPERTY_EMBEDDED,
 
1301
                                     NULL);
 
1302
        }
 
1303
#endif
 
1304
        cd_client_create_device (priv->client,
 
1305
                                 device_id,
 
1306
                                 CD_OBJECT_SCOPE_TEMP,
 
1307
                                 device_props,
 
1308
                                 NULL,
 
1309
                                 gcm_session_create_device_cb,
 
1310
                                 manager);
 
1311
        g_free (device_id);
 
1312
        if (device_props != NULL)
 
1313
                g_hash_table_unref (device_props);
 
1314
        if (edid != NULL)
 
1315
                g_object_unref (edid);
 
1316
}
 
1317
 
 
1318
 
 
1319
static void
 
1320
gnome_rr_screen_output_added_cb (GnomeRRScreen *screen,
 
1321
                                GnomeRROutput *output,
 
1322
                                GsdColorManager *manager)
 
1323
{
 
1324
        gcm_session_add_x11_output (manager, output);
 
1325
}
 
1326
 
 
1327
static void
 
1328
gcm_session_screen_removed_delete_device_cb (GObject *object, GAsyncResult *res, gpointer user_data)
 
1329
{
 
1330
        gboolean ret;
 
1331
        GError *error = NULL;
 
1332
 
 
1333
        /* deleted device */
 
1334
        ret = cd_client_delete_device_finish (CD_CLIENT (object),
 
1335
                                              res,
 
1336
                                              &error);
 
1337
        if (!ret) {
 
1338
                g_warning ("failed to delete device: %s",
 
1339
                           error->message);
 
1340
                g_error_free (error);
 
1341
        }
 
1342
}
 
1343
 
 
1344
static void
 
1345
gcm_session_screen_removed_find_device_cb (GObject *object, GAsyncResult *res, gpointer user_data)
 
1346
{
 
1347
        GError *error = NULL;
 
1348
        CdDevice *device;
 
1349
        GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
 
1350
 
 
1351
        device = cd_client_find_device_finish (manager->priv->client,
 
1352
                                               res,
 
1353
                                               &error);
 
1354
        if (device == NULL) {
 
1355
                g_warning ("failed to find device: %s",
 
1356
                           error->message);
 
1357
                g_error_free (error);
 
1358
                return;
 
1359
        }
 
1360
        g_debug ("output %s found, and will be removed",
 
1361
                 cd_device_get_object_path (device));
 
1362
        cd_client_delete_device (manager->priv->client,
 
1363
                                 device,
 
1364
                                 NULL,
 
1365
                                 gcm_session_screen_removed_delete_device_cb,
 
1366
                                 manager);
 
1367
        g_object_unref (device);
 
1368
}
 
1369
 
 
1370
static void
 
1371
gnome_rr_screen_output_removed_cb (GnomeRRScreen *screen,
 
1372
                                   GnomeRROutput *output,
 
1373
                                   GsdColorManager *manager)
 
1374
{
 
1375
        g_debug ("output %s removed",
 
1376
                 gnome_rr_output_get_name (output));
 
1377
        g_hash_table_remove (manager->priv->edid_cache,
 
1378
                             gnome_rr_output_get_name (output));
 
1379
        cd_client_find_device_by_property (manager->priv->client,
 
1380
                                           CD_DEVICE_METADATA_XRANDR_NAME,
 
1381
                                           gnome_rr_output_get_name (output),
 
1382
                                           NULL,
 
1383
                                           gcm_session_screen_removed_find_device_cb,
 
1384
                                           manager);
 
1385
}
 
1386
 
 
1387
static void
 
1388
gcm_session_get_devices_cb (GObject *object, GAsyncResult *res, gpointer user_data)
 
1389
{
 
1390
        CdDevice *device;
 
1391
        GError *error = NULL;
 
1392
        GPtrArray *array;
 
1393
        guint i;
 
1394
        GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
 
1395
 
 
1396
        array = cd_client_get_devices_finish (CD_CLIENT (object), res, &error);
 
1397
        if (array == NULL) {
 
1398
                g_warning ("failed to get devices: %s",
 
1399
                           error->message);
 
1400
                g_error_free (error);
 
1401
                goto out;
 
1402
        }
 
1403
        for (i = 0; i < array->len; i++) {
 
1404
                device = g_ptr_array_index (array, i);
 
1405
                gcm_session_device_assign (manager, device);
 
1406
        }
 
1407
out:
 
1408
        if (array != NULL)
 
1409
                g_ptr_array_unref (array);
 
1410
}
 
1411
 
 
1412
static void
 
1413
gcm_session_profile_gamma_find_device_cb (GObject *object,
 
1414
                                          GAsyncResult *res,
 
1415
                                          gpointer user_data)
 
1416
{
 
1417
        CdClient *client = CD_CLIENT (object);
 
1418
        CdDevice *device = NULL;
 
1419
        GError *error = NULL;
 
1420
        GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
 
1421
 
 
1422
        device = cd_client_find_device_by_property_finish (client,
 
1423
                                                           res,
 
1424
                                                           &error);
 
1425
        if (device == NULL) {
 
1426
                g_warning ("could not find device: %s",
 
1427
                           error->message);
 
1428
                g_error_free (error);
 
1429
                goto out;
 
1430
        }
 
1431
 
 
1432
        /* get properties */
 
1433
        cd_device_connect (device,
 
1434
                           NULL,
 
1435
                           gcm_session_device_assign_connect_cb,
 
1436
                           manager);
 
1437
out:
 
1438
        if (device != NULL)
 
1439
                g_object_unref (device);
 
1440
}
 
1441
 
 
1442
/* We have to reset the gamma tables each time as if the primary output
 
1443
 * has changed then different crtcs are going to be used.
 
1444
 * See https://bugzilla.gnome.org/show_bug.cgi?id=660164 for an example */
 
1445
static void
 
1446
gnome_rr_screen_output_changed_cb (GnomeRRScreen *screen,
 
1447
                                   GsdColorManager *manager)
 
1448
{
 
1449
        GnomeRROutput **outputs;
 
1450
        GsdColorManagerPrivate *priv = manager->priv;
 
1451
        guint i;
 
1452
 
 
1453
        /* get X11 outputs */
 
1454
        outputs = gnome_rr_screen_list_outputs (priv->x11_screen);
 
1455
        if (outputs == NULL) {
 
1456
                g_warning ("failed to get outputs");
 
1457
                return;
 
1458
        }
 
1459
        for (i = 0; outputs[i] != NULL; i++) {
 
1460
                if (!gnome_rr_output_is_connected (outputs[i]))
 
1461
                        continue;
 
1462
 
 
1463
                /* get CdDevice for this output */
 
1464
                cd_client_find_device_by_property (manager->priv->client,
 
1465
                                                   CD_DEVICE_METADATA_XRANDR_NAME,
 
1466
                                                   gnome_rr_output_get_name (outputs[i]),
 
1467
                                                   NULL,
 
1468
                                                   gcm_session_profile_gamma_find_device_cb,
 
1469
                                                   manager);
 
1470
        }
 
1471
 
 
1472
}
 
1473
 
 
1474
static void
 
1475
gcm_session_client_connect_cb (GObject *source_object,
 
1476
                               GAsyncResult *res,
 
1477
                               gpointer user_data)
 
1478
{
 
1479
        gboolean ret;
 
1480
        GError *error = NULL;
 
1481
        GnomeRROutput **outputs;
 
1482
        guint i;
 
1483
        GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
 
1484
        GsdColorManagerPrivate *priv = manager->priv;
 
1485
 
 
1486
        /* connected */
 
1487
        g_debug ("connected to colord");
 
1488
        ret = cd_client_connect_finish (manager->priv->client, res, &error);
 
1489
        if (!ret) {
 
1490
                g_warning ("failed to connect to colord: %s", error->message);
 
1491
                g_error_free (error);
 
1492
                return;
 
1493
        }
 
1494
 
 
1495
#if CD_CHECK_VERSION(0,1,12)
 
1496
        /* is there an available colord instance? */
 
1497
        ret = cd_client_get_has_server (manager->priv->client);
 
1498
        if (!ret) {
 
1499
                g_warning ("There is no colord server available");
 
1500
                goto out;
 
1501
        }
 
1502
#endif
 
1503
 
 
1504
        /* add profiles */
 
1505
        gcm_profile_store_search (priv->profile_store);
 
1506
 
 
1507
        /* add screens */
 
1508
        gnome_rr_screen_refresh (priv->x11_screen, &error);
 
1509
        if (error != NULL) {
 
1510
                g_warning ("failed to refresh: %s", error->message);
 
1511
                g_error_free (error);
 
1512
                goto out;
 
1513
        }
 
1514
 
 
1515
        /* get X11 outputs */
 
1516
        outputs = gnome_rr_screen_list_outputs (priv->x11_screen);
 
1517
        if (outputs == NULL) {
 
1518
                g_warning ("failed to get outputs");
 
1519
                goto out;
 
1520
        }
 
1521
        for (i = 0; outputs[i] != NULL; i++) {
 
1522
                if (gnome_rr_output_is_connected (outputs[i]))
 
1523
                        gcm_session_add_x11_output (manager, outputs[i]);
 
1524
        }
 
1525
 
 
1526
        /* only connect when colord is awake */
 
1527
        g_signal_connect (priv->x11_screen, "output-connected",
 
1528
                          G_CALLBACK (gnome_rr_screen_output_added_cb),
 
1529
                          manager);
 
1530
        g_signal_connect (priv->x11_screen, "output-disconnected",
 
1531
                          G_CALLBACK (gnome_rr_screen_output_removed_cb),
 
1532
                          manager);
 
1533
        g_signal_connect (priv->x11_screen, "changed",
 
1534
                          G_CALLBACK (gnome_rr_screen_output_changed_cb),
 
1535
                          manager);
 
1536
 
 
1537
        g_signal_connect (priv->client, "device-added",
 
1538
                          G_CALLBACK (gcm_session_device_added_assign_cb),
 
1539
                          manager);
 
1540
        g_signal_connect (priv->client, "device-changed",
 
1541
                          G_CALLBACK (gcm_session_device_changed_assign_cb),
 
1542
                          manager);
 
1543
 
 
1544
        /* set for each device that already exist */
 
1545
        cd_client_get_devices (priv->client, NULL,
 
1546
                               gcm_session_get_devices_cb,
 
1547
                               manager);
 
1548
out:
 
1549
        return;
 
1550
}
 
1551
 
 
1552
gboolean
 
1553
gsd_color_manager_start (GsdColorManager *manager,
 
1554
                         GError          **error)
 
1555
{
 
1556
        GsdColorManagerPrivate *priv = manager->priv;
 
1557
        gboolean ret = FALSE;
 
1558
 
 
1559
        g_debug ("Starting color manager");
 
1560
        gnome_settings_profile_start (NULL);
 
1561
 
 
1562
        /* coldplug the list of screens */
 
1563
        priv->x11_screen = gnome_rr_screen_new (gdk_screen_get_default (), error);
 
1564
        if (priv->x11_screen == NULL)
 
1565
                goto out;
 
1566
 
 
1567
        cd_client_connect (priv->client,
 
1568
                           NULL,
 
1569
                           gcm_session_client_connect_cb,
 
1570
                           manager);
 
1571
 
 
1572
        /* success */
 
1573
        ret = TRUE;
 
1574
out:
 
1575
        gnome_settings_profile_end (NULL);
 
1576
        return ret;
 
1577
}
 
1578
 
 
1579
void
 
1580
gsd_color_manager_stop (GsdColorManager *manager)
 
1581
{
 
1582
        g_debug ("Stopping color manager");
 
1583
 
 
1584
        g_clear_object (&manager->priv->settings);
 
1585
        g_clear_object (&manager->priv->client);
 
1586
        g_clear_object (&manager->priv->profile_store);
 
1587
        g_clear_object (&manager->priv->dmi);
 
1588
        g_clear_object (&manager->priv->session);
 
1589
        g_clear_pointer (&manager->priv->edid_cache, g_hash_table_destroy);
 
1590
        g_clear_pointer (&manager->priv->device_assign_hash, g_hash_table_destroy);
 
1591
        g_clear_object (&manager->priv->x11_screen);
 
1592
}
 
1593
 
 
1594
static void
 
1595
gcm_session_exec_control_center (GsdColorManager *manager)
 
1596
{
 
1597
        gboolean ret;
 
1598
        GError *error = NULL;
 
1599
        GAppInfo *app_info;
 
1600
        GdkAppLaunchContext *launch_context;
 
1601
 
 
1602
        /* setup the launch context so the startup notification is correct */
 
1603
        launch_context = gdk_display_get_app_launch_context (gdk_display_get_default ());
 
1604
        app_info = g_app_info_create_from_commandline (BINDIR "/gnome-control-center color",
 
1605
                                                       "gnome-control-center",
 
1606
                                                       G_APP_INFO_CREATE_SUPPORTS_STARTUP_NOTIFICATION,
 
1607
                                                       &error);
 
1608
        if (app_info == NULL) {
 
1609
                g_warning ("failed to create application info: %s",
 
1610
                           error->message);
 
1611
                g_error_free (error);
 
1612
                goto out;
 
1613
        }
 
1614
 
 
1615
        /* launch gnome-control-center */
 
1616
        ret = g_app_info_launch (app_info,
 
1617
                                 NULL,
 
1618
                                 G_APP_LAUNCH_CONTEXT (launch_context),
 
1619
                                 &error);
 
1620
        if (!ret) {
 
1621
                g_warning ("failed to launch gnome-control-center: %s",
 
1622
                           error->message);
 
1623
                g_error_free (error);
 
1624
                goto out;
 
1625
        }
 
1626
out:
 
1627
        g_object_unref (launch_context);
 
1628
        if (app_info != NULL)
 
1629
                g_object_unref (app_info);
 
1630
}
 
1631
 
 
1632
static void
 
1633
gcm_session_notify_cb (NotifyNotification *notification,
 
1634
                       gchar *action,
 
1635
                       gpointer user_data)
 
1636
{
 
1637
        GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
 
1638
 
 
1639
        if (g_strcmp0 (action, "recalibrate") == 0) {
 
1640
                notify_notification_close (notification, NULL);
 
1641
                gcm_session_exec_control_center (manager);
 
1642
        }
 
1643
}
 
1644
 
 
1645
static void
 
1646
closed_cb (NotifyNotification *notification, gpointer data)
 
1647
{
 
1648
        g_object_unref (notification);
 
1649
}
 
1650
 
 
1651
static gboolean
 
1652
gcm_session_notify_recalibrate (GsdColorManager *manager,
 
1653
                                const gchar *title,
 
1654
                                const gchar *message,
 
1655
                                CdDeviceKind kind)
 
1656
{
 
1657
        gboolean ret;
 
1658
        GError *error = NULL;
 
1659
        NotifyNotification *notification;
 
1660
        GsdColorManagerPrivate *priv = manager->priv;
 
1661
 
 
1662
        /* show a bubble */
 
1663
        notification = notify_notification_new (title, message, "preferences-color");
 
1664
        notify_notification_set_timeout (notification, GCM_SESSION_NOTIFY_TIMEOUT);
 
1665
        notify_notification_set_urgency (notification, NOTIFY_URGENCY_LOW);
 
1666
        notify_notification_set_app_name (notification, _("Color"));
 
1667
 
 
1668
        /* TRANSLATORS: button: this is to open GCM */
 
1669
        notify_notification_add_action (notification,
 
1670
                                        "recalibrate",
 
1671
                                        _("Recalibrate now"),
 
1672
                                        gcm_session_notify_cb,
 
1673
                                        priv, NULL);
 
1674
 
 
1675
        g_signal_connect (notification, "closed", G_CALLBACK (closed_cb), NULL);
 
1676
        ret = notify_notification_show (notification, &error);
 
1677
        if (!ret) {
 
1678
                g_warning ("failed to show notification: %s",
 
1679
                           error->message);
 
1680
                g_error_free (error);
 
1681
        }
 
1682
        return ret;
 
1683
}
 
1684
 
 
1685
static gchar *
 
1686
gcm_session_device_get_title (CdDevice *device)
 
1687
{
 
1688
        const gchar *vendor;
 
1689
        const gchar *model;
 
1690
 
 
1691
        model = cd_device_get_model (device);
 
1692
        vendor = cd_device_get_vendor (device);
 
1693
        if (model != NULL && vendor != NULL)
 
1694
                return g_strdup_printf ("%s - %s", vendor, model);
 
1695
        if (vendor != NULL)
 
1696
                return g_strdup (vendor);
 
1697
        if (model != NULL)
 
1698
                return g_strdup (model);
 
1699
        return g_strdup (cd_device_get_id (device));
 
1700
}
 
1701
 
 
1702
static void
 
1703
gcm_session_notify_device (GsdColorManager *manager, CdDevice *device)
 
1704
{
 
1705
        CdDeviceKind kind;
 
1706
        const gchar *title;
 
1707
        gchar *device_title = NULL;
 
1708
        gchar *message;
 
1709
        guint threshold;
 
1710
        glong since;
 
1711
        GsdColorManagerPrivate *priv = manager->priv;
 
1712
 
 
1713
        /* TRANSLATORS: this is when the device has not been recalibrated in a while */
 
1714
        title = _("Recalibration required");
 
1715
        device_title = gcm_session_device_get_title (device);
 
1716
 
 
1717
        /* check we care */
 
1718
        kind = cd_device_get_kind (device);
 
1719
        if (kind == CD_DEVICE_KIND_DISPLAY) {
 
1720
 
 
1721
                /* get from GSettings */
 
1722
                threshold = g_settings_get_uint (priv->settings,
 
1723
                                                 GCM_SETTINGS_RECALIBRATE_DISPLAY_THRESHOLD);
 
1724
 
 
1725
                /* TRANSLATORS: this is when the display has not been recalibrated in a while */
 
1726
                message = g_strdup_printf (_("The display '%s' should be recalibrated soon."),
 
1727
                                           device_title);
 
1728
        } else {
 
1729
 
 
1730
                /* get from GSettings */
 
1731
                threshold = g_settings_get_uint (priv->settings,
 
1732
                                                 GCM_SETTINGS_RECALIBRATE_PRINTER_THRESHOLD);
 
1733
 
 
1734
                /* TRANSLATORS: this is when the printer has not been recalibrated in a while */
 
1735
                message = g_strdup_printf (_("The printer '%s' should be recalibrated soon."),
 
1736
                                           device_title);
 
1737
        }
 
1738
 
 
1739
        /* check if we need to notify */
 
1740
        since = (g_get_real_time () - cd_device_get_modified (device)) / G_USEC_PER_SEC;
 
1741
        if (threshold > since)
 
1742
                gcm_session_notify_recalibrate (manager, title, message, kind);
 
1743
        g_free (device_title);
 
1744
        g_free (message);
 
1745
}
 
1746
 
 
1747
static void
 
1748
gcm_session_profile_connect_cb (GObject *object,
 
1749
                                GAsyncResult *res,
 
1750
                                gpointer user_data)
 
1751
{
 
1752
        const gchar *filename;
 
1753
        gboolean ret;
 
1754
        gchar *basename = NULL;
 
1755
        const gchar *data_source;
 
1756
        GError *error = NULL;
 
1757
        CdProfile *profile = CD_PROFILE (object);
 
1758
        GcmSessionAsyncHelper *helper = (GcmSessionAsyncHelper *) user_data;
 
1759
        GsdColorManager *manager = GSD_COLOR_MANAGER (helper->manager);
 
1760
 
 
1761
        ret = cd_profile_connect_finish (profile,
 
1762
                                         res,
 
1763
                                         &error);
 
1764
        if (!ret) {
 
1765
                g_warning ("failed to connect to profile: %s",
 
1766
                           error->message);
 
1767
                g_error_free (error);
 
1768
                goto out;
 
1769
        }
 
1770
 
 
1771
        /* ensure it's a profile generated by us */
 
1772
        data_source = cd_profile_get_metadata_item (profile,
 
1773
                                                    CD_PROFILE_METADATA_DATA_SOURCE);
 
1774
        if (data_source == NULL) {
 
1775
 
 
1776
                /* existing profiles from gnome-color-manager < 3.1
 
1777
                 * won't have the extra metadata values added */
 
1778
                filename = cd_profile_get_filename (profile);
 
1779
                if (filename == NULL)
 
1780
                        goto out;
 
1781
                basename = g_path_get_basename (filename);
 
1782
                if (!g_str_has_prefix (basename, "GCM")) {
 
1783
                        g_debug ("not a GCM profile for %s: %s",
 
1784
                                 cd_device_get_id (helper->device), filename);
 
1785
                        goto out;
 
1786
                }
 
1787
 
 
1788
        /* ensure it's been created from a calibration, rather than from
 
1789
         * auto-EDID */
 
1790
        } else if (g_strcmp0 (data_source,
 
1791
                   CD_PROFILE_METADATA_DATA_SOURCE_CALIB) != 0) {
 
1792
                g_debug ("not a calib profile for %s",
 
1793
                         cd_device_get_id (helper->device));
 
1794
                goto out;
 
1795
        }
 
1796
 
 
1797
        /* handle device */
 
1798
        gcm_session_notify_device (manager, helper->device);
 
1799
out:
 
1800
        gcm_session_async_helper_free (helper);
 
1801
        g_free (basename);
 
1802
}
 
1803
 
 
1804
static void
 
1805
gcm_session_device_connect_cb (GObject *object,
 
1806
                               GAsyncResult *res,
 
1807
                               gpointer user_data)
 
1808
{
 
1809
        gboolean ret;
 
1810
        GError *error = NULL;
 
1811
        CdDeviceKind kind;
 
1812
        CdProfile *profile = NULL;
 
1813
        CdDevice *device = CD_DEVICE (object);
 
1814
        GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
 
1815
        GcmSessionAsyncHelper *helper;
 
1816
 
 
1817
        ret = cd_device_connect_finish (device,
 
1818
                                        res,
 
1819
                                        &error);
 
1820
        if (!ret) {
 
1821
                g_warning ("failed to connect to device: %s",
 
1822
                           error->message);
 
1823
                g_error_free (error);
 
1824
                goto out;
 
1825
        }
 
1826
 
 
1827
        /* check we care */
 
1828
        kind = cd_device_get_kind (device);
 
1829
        if (kind != CD_DEVICE_KIND_DISPLAY &&
 
1830
            kind != CD_DEVICE_KIND_PRINTER)
 
1831
                goto out;
 
1832
 
 
1833
        /* ensure we have a profile */
 
1834
        profile = cd_device_get_default_profile (device);
 
1835
        if (profile == NULL) {
 
1836
                g_debug ("no profile set for %s", cd_device_get_id (device));
 
1837
                goto out;
 
1838
        }
 
1839
 
 
1840
        /* connect to the profile */
 
1841
        helper = g_new0 (GcmSessionAsyncHelper, 1);
 
1842
        helper->manager = g_object_ref (manager);
 
1843
        helper->device = g_object_ref (device);
 
1844
        cd_profile_connect (profile,
 
1845
                            NULL,
 
1846
                            gcm_session_profile_connect_cb,
 
1847
                            helper);
 
1848
out:
 
1849
        if (profile != NULL)
 
1850
                g_object_unref (profile);
 
1851
}
 
1852
 
 
1853
static void
 
1854
gcm_session_device_added_notify_cb (CdClient *client,
 
1855
                                    CdDevice *device,
 
1856
                                    GsdColorManager *manager)
 
1857
{
 
1858
        /* connect to the device to get properties */
 
1859
        cd_device_connect (device,
 
1860
                           NULL,
 
1861
                           gcm_session_device_connect_cb,
 
1862
                           manager);
 
1863
}
 
1864
 
 
1865
static gchar *
 
1866
gcm_session_get_precooked_md5 (cmsHPROFILE lcms_profile)
 
1867
{
 
1868
        cmsUInt8Number profile_id[16];
 
1869
        gboolean md5_precooked = FALSE;
 
1870
        guint i;
 
1871
        gchar *md5 = NULL;
 
1872
 
 
1873
        /* check to see if we have a pre-cooked MD5 */
 
1874
        cmsGetHeaderProfileID (lcms_profile, profile_id);
 
1875
        for (i = 0; i < 16; i++) {
 
1876
                if (profile_id[i] != 0) {
 
1877
                        md5_precooked = TRUE;
 
1878
                        break;
 
1879
                }
 
1880
        }
 
1881
        if (!md5_precooked)
 
1882
                goto out;
 
1883
 
 
1884
        /* convert to a hex string */
 
1885
        md5 = g_new0 (gchar, 32 + 1);
 
1886
        for (i = 0; i < 16; i++)
 
1887
                g_snprintf (md5 + i*2, 3, "%02x", profile_id[i]);
 
1888
out:
 
1889
        return md5;
 
1890
}
 
1891
 
 
1892
static gchar *
 
1893
gcm_session_get_md5_for_filename (const gchar *filename,
 
1894
                                  GError **error)
 
1895
{
 
1896
        gboolean ret;
 
1897
        gchar *checksum = NULL;
 
1898
        gchar *data = NULL;
 
1899
        gsize length;
 
1900
        cmsHPROFILE lcms_profile = NULL;
 
1901
 
 
1902
        /* get the internal profile id, if it exists */
 
1903
        lcms_profile = cmsOpenProfileFromFile (filename, "r");
 
1904
        if (lcms_profile == NULL) {
 
1905
                g_set_error_literal (error,
 
1906
                                     GSD_COLOR_MANAGER_ERROR,
 
1907
                                     GSD_COLOR_MANAGER_ERROR_FAILED,
 
1908
                                     "failed to load: not an ICC profile");
 
1909
                goto out;
 
1910
        }
 
1911
        checksum = gcm_session_get_precooked_md5 (lcms_profile);
 
1912
        if (checksum != NULL)
 
1913
                goto out;
 
1914
 
 
1915
        /* generate checksum */
 
1916
        ret = g_file_get_contents (filename, &data, &length, error);
 
1917
        if (!ret)
 
1918
                goto out;
 
1919
        checksum = g_compute_checksum_for_data (G_CHECKSUM_MD5,
 
1920
                                                (const guchar *) data,
 
1921
                                                length);
 
1922
out:
 
1923
        g_free (data);
 
1924
        if (lcms_profile != NULL)
 
1925
                cmsCloseProfile (lcms_profile);
 
1926
        return checksum;
 
1927
}
 
1928
 
 
1929
static void
 
1930
gcm_session_create_profile_cb (GObject *object,
 
1931
                               GAsyncResult *res,
 
1932
                               gpointer user_data)
 
1933
{
 
1934
        CdProfile *profile;
 
1935
        GError *error = NULL;
 
1936
        CdClient *client = CD_CLIENT (object);
 
1937
 
 
1938
        profile = cd_client_create_profile_finish (client, res, &error);
 
1939
        if (profile == NULL) {
 
1940
                if (error->domain != CD_CLIENT_ERROR ||
 
1941
                    error->code != CD_CLIENT_ERROR_ALREADY_EXISTS)
 
1942
                        g_warning ("%s", error->message);
 
1943
                g_error_free (error);
 
1944
                return;
 
1945
        }
 
1946
        g_object_unref (profile);
 
1947
}
 
1948
 
 
1949
static void
 
1950
gcm_session_profile_store_added_cb (GcmProfileStore *profile_store,
 
1951
                                    const gchar *filename,
 
1952
                                    GsdColorManager *manager)
 
1953
{
 
1954
        gchar *checksum = NULL;
 
1955
        gchar *profile_id = NULL;
 
1956
        GError *error = NULL;
 
1957
        GHashTable *profile_props = NULL;
 
1958
        GsdColorManagerPrivate *priv = manager->priv;
 
1959
 
 
1960
        g_debug ("profile %s added", filename);
 
1961
 
 
1962
        /* generate ID */
 
1963
        checksum = gcm_session_get_md5_for_filename (filename, &error);
 
1964
        if (checksum == NULL) {
 
1965
                g_debug ("failed to get profile checksum for %s: %s",
 
1966
                         filename, error->message);
 
1967
                g_error_free (error);
 
1968
                goto out;
 
1969
        }
 
1970
        profile_id = g_strdup_printf ("icc-%s", checksum);
 
1971
        profile_props = g_hash_table_new_full (g_str_hash, g_str_equal,
 
1972
                                               NULL, NULL);
 
1973
        g_hash_table_insert (profile_props,
 
1974
                             CD_PROFILE_PROPERTY_FILENAME,
 
1975
                             (gpointer) filename);
 
1976
        g_hash_table_insert (profile_props,
 
1977
                             CD_PROFILE_METADATA_FILE_CHECKSUM,
 
1978
                             (gpointer) checksum);
 
1979
        cd_client_create_profile (priv->client,
 
1980
                                  profile_id,
 
1981
                                  CD_OBJECT_SCOPE_TEMP,
 
1982
                                  profile_props,
 
1983
                                  NULL,
 
1984
                                  gcm_session_create_profile_cb,
 
1985
                                  manager);
 
1986
out:
 
1987
        g_free (checksum);
 
1988
        g_free (profile_id);
 
1989
        if (profile_props != NULL)
 
1990
                g_hash_table_unref (profile_props);
 
1991
}
 
1992
 
 
1993
static void
 
1994
gcm_session_delete_profile_cb (GObject *object,
 
1995
                               GAsyncResult *res,
 
1996
                               gpointer user_data)
 
1997
{
 
1998
        gboolean ret;
 
1999
        GError *error = NULL;
 
2000
        CdClient *client = CD_CLIENT (object);
 
2001
 
 
2002
        ret = cd_client_delete_profile_finish (client, res, &error);
 
2003
        if (!ret) {
 
2004
                g_warning ("%s", error->message);
 
2005
                g_error_free (error);
 
2006
        }
 
2007
}
 
2008
 
 
2009
static void
 
2010
gcm_session_find_profile_by_filename_cb (GObject *object,
 
2011
                                         GAsyncResult *res,
 
2012
                                         gpointer user_data)
 
2013
{
 
2014
        GError *error = NULL;
 
2015
        CdProfile *profile;
 
2016
        CdClient *client = CD_CLIENT (object);
 
2017
        GsdColorManager *manager = GSD_COLOR_MANAGER (user_data);
 
2018
 
 
2019
        profile = cd_client_find_profile_by_filename_finish (client, res, &error);
 
2020
        if (profile == NULL) {
 
2021
                g_warning ("%s", error->message);
 
2022
                g_error_free (error);
 
2023
                goto out;
 
2024
        }
 
2025
 
 
2026
        /* remove it from colord */
 
2027
        cd_client_delete_profile (manager->priv->client,
 
2028
                                  profile,
 
2029
                                  NULL,
 
2030
                                  gcm_session_delete_profile_cb,
 
2031
                                  manager);
 
2032
out:
 
2033
        if (profile != NULL)
 
2034
                g_object_unref (profile);
 
2035
}
 
2036
 
 
2037
static void
 
2038
gcm_session_profile_store_removed_cb (GcmProfileStore *profile_store,
 
2039
                                      const gchar *filename,
 
2040
                                      GsdColorManager *manager)
 
2041
{
 
2042
        /* find the ID for the filename */
 
2043
        g_debug ("filename %s removed", filename);
 
2044
        cd_client_find_profile_by_filename (manager->priv->client,
 
2045
                                            filename,
 
2046
                                            NULL,
 
2047
                                            gcm_session_find_profile_by_filename_cb,
 
2048
                                            manager);
 
2049
}
 
2050
 
 
2051
static void
 
2052
gcm_session_sensor_added_cb (CdClient *client,
 
2053
                             CdSensor *sensor,
 
2054
                             GsdColorManager *manager)
 
2055
{
 
2056
        ca_context_play (ca_gtk_context_get (), 0,
 
2057
                         CA_PROP_EVENT_ID, "device-added",
 
2058
                         /* TRANSLATORS: this is the application name */
 
2059
                         CA_PROP_APPLICATION_NAME, _("GNOME Settings Daemon Color Plugin"),
 
2060
                        /* TRANSLATORS: this is a sound description */
 
2061
                         CA_PROP_EVENT_DESCRIPTION, _("Color calibration device added"), NULL);
 
2062
 
 
2063
        /* open up the color prefs window */
 
2064
        gcm_session_exec_control_center (manager);
 
2065
}
 
2066
 
 
2067
static void
 
2068
gcm_session_sensor_removed_cb (CdClient *client,
 
2069
                               CdSensor *sensor,
 
2070
                               GsdColorManager *manager)
 
2071
{
 
2072
        ca_context_play (ca_gtk_context_get (), 0,
 
2073
                         CA_PROP_EVENT_ID, "device-removed",
 
2074
                         /* TRANSLATORS: this is the application name */
 
2075
                         CA_PROP_APPLICATION_NAME, _("GNOME Settings Daemon Color Plugin"),
 
2076
                        /* TRANSLATORS: this is a sound description */
 
2077
                         CA_PROP_EVENT_DESCRIPTION, _("Color calibration device removed"), NULL);
 
2078
}
 
2079
 
 
2080
static gboolean
 
2081
has_changed (char       **strv,
 
2082
             const char  *str)
 
2083
{
 
2084
        guint i;
 
2085
        for (i = 0; strv[i] != NULL; i++) {
 
2086
                if (g_str_equal (str, strv[i]))
 
2087
                        return TRUE;
 
2088
        }
 
2089
        return FALSE;
 
2090
}
 
2091
 
 
2092
static void
 
2093
gcm_session_active_changed_cb (GDBusProxy      *session,
 
2094
                               GVariant        *changed,
 
2095
                               char           **invalidated,
 
2096
                               GsdColorManager *manager)
 
2097
{
 
2098
        GsdColorManagerPrivate *priv = manager->priv;
 
2099
        GVariant *active_v = NULL;
 
2100
        gboolean is_active;
 
2101
 
 
2102
        if (has_changed (invalidated, "SessionIsActive"))
 
2103
                return;
 
2104
 
 
2105
        /* not yet connected to the daemon */
 
2106
        if (!cd_client_get_connected (priv->client))
 
2107
                return;
 
2108
 
 
2109
        active_v = g_dbus_proxy_get_cached_property (session, "SessionIsActive");
 
2110
        g_return_if_fail (active_v != NULL);
 
2111
        is_active = g_variant_get_boolean (active_v);
 
2112
        g_variant_unref (active_v);
 
2113
 
 
2114
        /* When doing the fast-user-switch into a new account, load the
 
2115
         * new users chosen profiles.
 
2116
         *
 
2117
         * If this is the first time the GnomeSettingsSession has been
 
2118
         * loaded, then we'll get a change from unknown to active
 
2119
         * and we want to avoid reprobing the devices for that.
 
2120
         */
 
2121
        if (is_active && !priv->session_is_active) {
 
2122
                g_debug ("Done switch to new account, reload devices");
 
2123
                cd_client_get_devices (manager->priv->client, NULL,
 
2124
                                       gcm_session_get_devices_cb,
 
2125
                                       manager);
 
2126
        }
 
2127
        priv->session_is_active = is_active;
 
2128
}
 
2129
 
 
2130
static void
 
2131
gsd_color_manager_class_init (GsdColorManagerClass *klass)
 
2132
{
 
2133
        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
 
2134
 
 
2135
        object_class->finalize = gsd_color_manager_finalize;
 
2136
 
 
2137
        g_type_class_add_private (klass, sizeof (GsdColorManagerPrivate));
 
2138
}
 
2139
 
 
2140
static void
 
2141
gsd_color_manager_init (GsdColorManager *manager)
 
2142
{
 
2143
        GsdColorManagerPrivate *priv;
 
2144
        priv = manager->priv = GSD_COLOR_MANAGER_GET_PRIVATE (manager);
 
2145
 
 
2146
        /* track the active session */
 
2147
        priv->session = gnome_settings_session_get_session_proxy ();
 
2148
        g_signal_connect (priv->session, "g-properties-changed",
 
2149
                          G_CALLBACK (gcm_session_active_changed_cb), manager);
 
2150
 
 
2151
        /* set the _ICC_PROFILE atoms on the root screen */
 
2152
        priv->gdk_window = gdk_screen_get_root_window (gdk_screen_get_default ());
 
2153
 
 
2154
        /* parsing the EDID is expensive */
 
2155
        priv->edid_cache = g_hash_table_new_full (g_str_hash,
 
2156
                                                  g_str_equal,
 
2157
                                                  g_free,
 
2158
                                                  g_object_unref);
 
2159
 
 
2160
        /* we don't want to assign devices multiple times at startup */
 
2161
        priv->device_assign_hash = g_hash_table_new_full (g_str_hash,
 
2162
                                                          g_str_equal,
 
2163
                                                          g_free,
 
2164
                                                          NULL);
 
2165
 
 
2166
        /* use DMI data for internal panels */
 
2167
        priv->dmi = gcm_dmi_new ();
 
2168
 
 
2169
        priv->settings = g_settings_new ("org.gnome.settings-daemon.plugins.color");
 
2170
        priv->client = cd_client_new ();
 
2171
        g_signal_connect (priv->client, "device-added",
 
2172
                          G_CALLBACK (gcm_session_device_added_notify_cb),
 
2173
                          manager);
 
2174
        g_signal_connect (priv->client, "sensor-added",
 
2175
                          G_CALLBACK (gcm_session_sensor_added_cb),
 
2176
                          manager);
 
2177
        g_signal_connect (priv->client, "sensor-removed",
 
2178
                          G_CALLBACK (gcm_session_sensor_removed_cb),
 
2179
                          manager);
 
2180
 
 
2181
        /* have access to all user profiles */
 
2182
        priv->profile_store = gcm_profile_store_new ();
 
2183
        g_signal_connect (priv->profile_store, "added",
 
2184
                          G_CALLBACK (gcm_session_profile_store_added_cb),
 
2185
                          manager);
 
2186
        g_signal_connect (priv->profile_store, "removed",
 
2187
                          G_CALLBACK (gcm_session_profile_store_removed_cb),
 
2188
                          manager);
 
2189
}
 
2190
 
 
2191
static void
 
2192
gsd_color_manager_finalize (GObject *object)
 
2193
{
 
2194
        GsdColorManager *manager;
 
2195
 
 
2196
        g_return_if_fail (object != NULL);
 
2197
        g_return_if_fail (GSD_IS_COLOR_MANAGER (object));
 
2198
 
 
2199
        manager = GSD_COLOR_MANAGER (object);
 
2200
 
 
2201
        g_clear_object (&manager->priv->settings);
 
2202
        g_clear_object (&manager->priv->client);
 
2203
        g_clear_object (&manager->priv->profile_store);
 
2204
        g_clear_object (&manager->priv->dmi);
 
2205
        g_clear_object (&manager->priv->session);
 
2206
        g_clear_pointer (&manager->priv->edid_cache, g_hash_table_destroy);
 
2207
        g_clear_pointer (&manager->priv->device_assign_hash, g_hash_table_destroy);
 
2208
        g_clear_object (&manager->priv->x11_screen);
 
2209
 
 
2210
        G_OBJECT_CLASS (gsd_color_manager_parent_class)->finalize (object);
 
2211
}
 
2212
 
 
2213
GsdColorManager *
 
2214
gsd_color_manager_new (void)
 
2215
{
 
2216
        if (manager_object != NULL) {
 
2217
                g_object_ref (manager_object);
 
2218
        } else {
 
2219
                manager_object = g_object_new (GSD_TYPE_COLOR_MANAGER, NULL);
 
2220
                g_object_add_weak_pointer (manager_object,
 
2221
                                           (gpointer *) &manager_object);
 
2222
        }
 
2223
 
 
2224
        return GSD_COLOR_MANAGER (manager_object);
 
2225
}