blob: 8af4e5cd1fd3d55374158795ebd096673e5933ca [file] [log] [blame]
/*
* Color management routines for the CUPS scheduler.
*
* Copyright 2007-2014 by Apple Inc.
* Copyright 1997-2007 by Easy Software Products, all rights reserved.
*
* Licensed under Apache License v2.0. See the file "LICENSE" for more information.
*
* Original DBUS/colord code is Copyright 2011 Red Hat, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Include necessary headers...
*/
#include "cupsd.h"
#include <cups/ppd-private.h>
#ifdef __APPLE__
# include <ApplicationServices/ApplicationServices.h>
extern CFUUIDRef ColorSyncCreateUUIDFromUInt32(unsigned id);
# include <CoreFoundation/CoreFoundation.h>
#elif defined(HAVE_DBUS)
# include <dbus/dbus.h>
/*
* Defines used by colord. See the reference docs for further details:
*
* http://colord.hughsie.com/api/ref-dbus.html
*/
# define COLORD_SCOPE_NORMAL "normal"
/* System scope */
# define COLORD_SCOPE_TEMP "temp" /* Process scope */
# define COLORD_SCOPE_DISK "disk" /* Lives forever, as stored in DB */
# define COLORD_RELATION_SOFT "soft" /* Mapping is not default */
# define COLORD_RELATION_HARD "hard" /* Explicitly mapped profile */
# define COLORD_SPACE_RGB "rgb" /* RGB colorspace */
# define COLORD_SPACE_CMYK "cmyk" /* CMYK colorspace */
# define COLORD_SPACE_GRAY "gray" /* Gray colorspace */
# define COLORD_SPACE_UNKNOWN "unknown"
/* Unknown colorspace */
# define COLORD_MODE_PHYSICAL "physical"
/* Actual device */
# define COLORD_MODE_VIRTUAL "virtual"
/* Virtual device with no hardware */
# define COLORD_KIND_PRINTER "printer"
/* printing output device */
# define COLORD_DBUS_SERVICE "org.freedesktop.ColorManager"
# define COLORD_DBUS_INTERFACE "org.freedesktop.ColorManager"
# define COLORD_DBUS_INTERFACE_DEVICE "org.freedesktop.ColorManager.Device"
# define COLORD_DBUS_PATH "/org/freedesktop/ColorManager"
/* Path for color management system */
# define COLORD_DBUS_TIMEOUT 5000 /* Timeout for connecting to colord in ms */
#endif /* __APPLE__ */
/*
* Local globals...
*/
#if !defined(__APPLE__) && defined(HAVE_DBUS)
static DBusConnection *colord_con = NULL;
/* DBUS connection for colord */
#endif /* !__APPLE__ && HAVE_DBUS */
/*
* Local functions...
*/
#ifdef __APPLE__
static void apple_init_profile(ppd_file_t *ppd, cups_array_t *languages,
CFMutableDictionaryRef profile,
unsigned id, const char *name,
const char *text, const char *iccfile);
static void apple_register_profiles(cupsd_printer_t *p);
static void apple_unregister_profiles(cupsd_printer_t *p);
#elif defined(HAVE_DBUS)
static void colord_create_device(cupsd_printer_t *p, ppd_file_t *ppd,
cups_array_t *profiles,
const char *colorspace, char **format,
const char *relation, const char *scope);
static void colord_create_profile(cups_array_t *profiles,
const char *printer_name,
const char *qualifier,
const char *colorspace,
char **format, const char *iccfile,
const char *scope);
static void colord_delete_device(const char *device_id);
static void colord_device_add_profile(const char *device_path,
const char *profile_path,
const char *relation);
static void colord_dict_add_strings(DBusMessageIter *dict,
const char *key, const char *value);
static char *colord_find_device(const char *device_id);
static void colord_get_qualifier_format(ppd_file_t *ppd, char *format[3]);
static void colord_register_printer(cupsd_printer_t *p);
static void colord_unregister_printer(cupsd_printer_t *p);
#endif /* __APPLE__ */
/*
* 'cupsdRegisterColor()' - Register vendor color profiles in a PPD file.
*/
void
cupsdRegisterColor(cupsd_printer_t *p) /* I - Printer */
{
#ifdef __APPLE__
if (!RunUser)
{
apple_unregister_profiles(p);
apple_register_profiles(p);
}
#elif defined(HAVE_DBUS)
if (!RunUser)
{
colord_unregister_printer(p);
colord_register_printer(p);
}
#endif /* __APPLE__ */
}
/*
* 'cupsdStartColor()' - Initialize color management.
*/
void
cupsdStartColor(void)
{
#if !defined(__APPLE__) && defined(HAVE_DBUS)
cupsd_printer_t *p; /* Current printer */
colord_con = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
p;
p = (cupsd_printer_t *)cupsArrayNext(Printers))
cupsdRegisterColor(p);
#endif /* !__APPLE__ && HAVE_DBUS */
}
/*
* 'cupsdStopColor()' - Shutdown color management.
*/
void
cupsdStopColor(void)
{
#if !defined(__APPLE__) && defined(HAVE_DBUS)
if (colord_con)
dbus_connection_unref(colord_con);
colord_con = NULL;
#endif /* !__APPLE__ && HAVE_DBUS */
}
/*
* 'cupsdUnregisterColor()' - Unregister vendor color profiles in a PPD file.
*/
void
cupsdUnregisterColor(cupsd_printer_t *p)/* I - Printer */
{
#ifdef __APPLE__
if (!RunUser)
apple_unregister_profiles(p);
#elif defined(HAVE_DBUS)
if (!RunUser)
colord_unregister_printer(p);
#endif /* __APPLE__ */
}
#ifdef __APPLE__
/*
* 'apple_init_profile()' - Initialize a color profile.
*/
static void
apple_init_profile(
ppd_file_t *ppd, /* I - PPD file */
cups_array_t *languages, /* I - Languages in the PPD file */
CFMutableDictionaryRef profile, /* I - Profile dictionary */
unsigned id, /* I - Profile ID */
const char *name, /* I - Profile name */
const char *text, /* I - Profile UI text */
const char *iccfile) /* I - ICC filename */
{
CFURLRef url; /* URL for profile filename */
CFMutableDictionaryRef dict; /* Dictionary for name */
char *language; /* Current language */
ppd_attr_t *attr; /* Profile attribute */
CFStringRef cflang, /* Language string */
cftext; /* Localized text */
(void)id;
/*
* Build the profile name dictionary...
*/
dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (!dict)
{
cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to initialize profile \"%s\".",
iccfile);
return;
}
cftext = CFStringCreateWithCString(kCFAllocatorDefault, text,
kCFStringEncodingUTF8);
if (cftext)
{
CFDictionarySetValue(dict, CFSTR("en_US"), cftext);
CFRelease(cftext);
}
if (languages)
{
/*
* Find localized names for the color profiles...
*/
cupsArraySave(ppd->sorted_attrs);
for (language = (char *)cupsArrayFirst(languages);
language;
language = (char *)cupsArrayNext(languages))
{
if (iccfile)
{
if ((attr = _ppdLocalizedAttr(ppd, "cupsICCProfile", name,
language)) == NULL)
attr = _ppdLocalizedAttr(ppd, "APTiogaProfile", name, language);
}
else
attr = _ppdLocalizedAttr(ppd, "ColorModel", name, language);
if (attr && attr->text[0])
{
cflang = CFStringCreateWithCString(kCFAllocatorDefault, language,
kCFStringEncodingUTF8);
cftext = CFStringCreateWithCString(kCFAllocatorDefault, attr->text,
kCFStringEncodingUTF8);
if (cflang && cftext)
CFDictionarySetValue(dict, cflang, cftext);
if (cflang)
CFRelease(cflang);
if (cftext)
CFRelease(cftext);
}
}
cupsArrayRestore(ppd->sorted_attrs);
}
/*
* Fill in the profile data...
*/
if (iccfile && *iccfile)
{
url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)iccfile, (CFIndex)strlen(iccfile), false);
if (url)
{
CFDictionarySetValue(profile, kColorSyncDeviceProfileURL, url);
CFRelease(url);
}
}
CFDictionarySetValue(profile, kColorSyncDeviceModeDescriptions, dict);
CFRelease(dict);
}
/*
* 'apple_register_profiles()' - Register color profiles for a printer.
*/
static void
apple_register_profiles(
cupsd_printer_t *p) /* I - Printer */
{
int i; /* Looping var */
char ppdfile[1024], /* PPD filename */
iccfile[1024], /* ICC filename */
selector[PPD_MAX_NAME];
/* Profile selection string */
ppd_file_t *ppd; /* PPD file */
ppd_attr_t *attr, /* Profile attributes */
*profileid_attr,/* cupsProfileID attribute */
*q1_attr, /* ColorModel (or other) qualifier */
*q2_attr, /* MediaType (or other) qualifier */
*q3_attr; /* Resolution (or other) qualifier */
char q_keyword[PPD_MAX_NAME];
/* Qualifier keyword */
const char *q1_choice, /* ColorModel (or other) choice */
*q2_choice, /* MediaType (or other) choice */
*q3_choice; /* Resolution (or other) choice */
ppd_option_t *cm_option; /* Color model option */
ppd_choice_t *cm_choice; /* Color model choice */
int num_profiles; /* Number of profiles */
OSStatus error = 0; /* Last error */
unsigned device_id, /* Printer device ID */
profile_id = 0, /* Profile ID */
default_profile_id = 0;
/* Default profile ID */
CFMutableDictionaryRef device_name; /* Printer device name dictionary */
CFStringRef printer_name; /* Printer name string */
cups_array_t *languages; /* Languages array */
CFMutableDictionaryRef profiles, /* Dictionary of profiles */
profile; /* Current profile info dictionary */
CFStringRef dict_key; /* Key in factory profile dictionary */
/*
* Make sure ColorSync is available...
*/
if (&ColorSyncRegisterDevice == NULL)
return;
/*
* Try opening the PPD file for this printer...
*/
snprintf(ppdfile, sizeof(ppdfile), "%s/ppd/%s.ppd", ServerRoot, p->name);
if ((ppd = _ppdOpenFile(ppdfile, _PPD_LOCALIZATION_ICC_PROFILES)) == NULL)
return;
/*
* See if we have any profiles...
*/
for (num_profiles = 0, attr = ppdFindAttr(ppd, "cupsICCProfile", NULL);
attr;
attr = ppdFindNextAttr(ppd, "cupsICCProfile", NULL))
if (attr->spec[0] && attr->value && attr->value[0])
{
if (attr->value[0] != '/')
snprintf(iccfile, sizeof(iccfile), "%s/profiles/%s", DataDir,
attr->value);
else
strlcpy(iccfile, attr->value, sizeof(iccfile));
if (access(iccfile, 0))
{
cupsdLogMessage(CUPSD_LOG_ERROR,
"%s: ICC Profile \"%s\" does not exist.", p->name,
iccfile);
cupsdSetPrinterReasons(p, "+cups-missing-filter-warning");
continue;
}
num_profiles ++;
}
/*
* Create a dictionary for the factory profiles...
*/
profiles = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (!profiles)
{
cupsdLogMessage(CUPSD_LOG_ERROR,
"Unable to allocate memory for factory profiles.");
ppdClose(ppd);
return;
}
/*
* If we have profiles, add them...
*/
if (num_profiles > 0)
{
/*
* For CUPS PPDs, figure out the default profile selector values...
*/
if ((attr = ppdFindAttr(ppd, "cupsICCQualifier1", NULL)) != NULL &&
attr->value && attr->value[0])
{
snprintf(q_keyword, sizeof(q_keyword), "Default%s", attr->value);
q1_attr = ppdFindAttr(ppd, q_keyword, NULL);
}
else if ((q1_attr = ppdFindAttr(ppd, "DefaultColorModel", NULL)) == NULL)
q1_attr = ppdFindAttr(ppd, "DefaultColorSpace", NULL);
if (q1_attr && q1_attr->value && q1_attr->value[0])
q1_choice = q1_attr->value;
else
q1_choice = "";
if ((attr = ppdFindAttr(ppd, "cupsICCQualifier2", NULL)) != NULL &&
attr->value && attr->value[0])
{
snprintf(q_keyword, sizeof(q_keyword), "Default%s", attr->value);
q2_attr = ppdFindAttr(ppd, q_keyword, NULL);
}
else
q2_attr = ppdFindAttr(ppd, "DefaultMediaType", NULL);
if (q2_attr && q2_attr->value && q2_attr->value[0])
q2_choice = q2_attr->value;
else
q2_choice = NULL;
if ((attr = ppdFindAttr(ppd, "cupsICCQualifier3", NULL)) != NULL &&
attr->value && attr->value[0])
{
snprintf(q_keyword, sizeof(q_keyword), "Default%s", attr->value);
q3_attr = ppdFindAttr(ppd, q_keyword, NULL);
}
else
q3_attr = ppdFindAttr(ppd, "DefaultResolution", NULL);
if (q3_attr && q3_attr->value && q3_attr->value[0])
q3_choice = q3_attr->value;
else
q3_choice = NULL;
/*
* Loop through the profiles listed in the PPD...
*/
languages = _ppdGetLanguages(ppd);
for (attr = ppdFindAttr(ppd, "cupsICCProfile", NULL);
attr;
attr = ppdFindNextAttr(ppd, "cupsICCProfile", NULL))
if (attr->spec[0] && attr->value && attr->value[0])
{
/*
* Add this profile...
*/
if (attr->value[0] != '/')
snprintf(iccfile, sizeof(iccfile), "%s/profiles/%s", DataDir,
attr->value);
else
strlcpy(iccfile, attr->value, sizeof(iccfile));
if (_cupsFileCheck(iccfile, _CUPS_FILE_CHECK_FILE, !RunUser,
cupsdLogFCMessage, p))
iccfile[0] = '\0';
cupsArraySave(ppd->sorted_attrs);
if ((profileid_attr = ppdFindAttr(ppd, "cupsProfileID",
attr->spec)) != NULL &&
profileid_attr->value && isdigit(profileid_attr->value[0] & 255))
profile_id = (unsigned)strtoul(profileid_attr->value, NULL, 10);
else
profile_id = _ppdHashName(attr->spec);
cupsArrayRestore(ppd->sorted_attrs);
profile = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (!profile)
{
cupsdLogMessage(CUPSD_LOG_ERROR,
"Unable to allocate memory for color profile.");
CFRelease(profiles);
ppdClose(ppd);
return;
}
apple_init_profile(ppd, languages, profile, profile_id, attr->spec,
attr->text[0] ? attr->text : attr->spec, iccfile);
dict_key = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
CFSTR("%u"), profile_id);
if (dict_key)
{
CFDictionarySetValue(profiles, dict_key, profile);
CFRelease(dict_key);
}
CFRelease(profile);
/*
* See if this is the default profile...
*/
if (!default_profile_id && q1_choice && q2_choice && q3_choice)
{
snprintf(selector, sizeof(selector), "%s.%s.%s", q1_choice, q2_choice,
q3_choice);
if (!strcmp(selector, attr->spec))
default_profile_id = profile_id;
}
if (!default_profile_id && q1_choice && q2_choice)
{
snprintf(selector, sizeof(selector), "%s.%s.", q1_choice, q2_choice);
if (!strcmp(selector, attr->spec))
default_profile_id = profile_id;
}
if (!default_profile_id && q1_choice && q3_choice)
{
snprintf(selector, sizeof(selector), "%s..%s", q1_choice, q3_choice);
if (!strcmp(selector, attr->spec))
default_profile_id = profile_id;
}
if (!default_profile_id && q1_choice)
{
snprintf(selector, sizeof(selector), "%s..", q1_choice);
if (!strcmp(selector, attr->spec))
default_profile_id = profile_id;
}
if (!default_profile_id && q2_choice && q3_choice)
{
snprintf(selector, sizeof(selector), ".%s.%s", q2_choice, q3_choice);
if (!strcmp(selector, attr->spec))
default_profile_id = profile_id;
}
if (!default_profile_id && q2_choice)
{
snprintf(selector, sizeof(selector), ".%s.", q2_choice);
if (!strcmp(selector, attr->spec))
default_profile_id = profile_id;
}
if (!default_profile_id && q3_choice)
{
snprintf(selector, sizeof(selector), "..%s", q3_choice);
if (!strcmp(selector, attr->spec))
default_profile_id = profile_id;
}
}
_ppdFreeLanguages(languages);
}
else if ((cm_option = ppdFindOption(ppd, "ColorModel")) != NULL)
{
/*
* Extract profiles from ColorModel option...
*/
const char *profile_name; /* Name of generic profile */
num_profiles = cm_option->num_choices;
for (i = cm_option->num_choices, cm_choice = cm_option->choices;
i > 0;
i --, cm_choice ++)
{
if (!strcmp(cm_choice->choice, "Gray") ||
!strcmp(cm_choice->choice, "Black"))
profile_name = "Gray";
else if (!strcmp(cm_choice->choice, "RGB") ||
!strcmp(cm_choice->choice, "CMY"))
profile_name = "RGB";
else if (!strcmp(cm_choice->choice, "CMYK") ||
!strcmp(cm_choice->choice, "KCMY"))
profile_name = "CMYK";
else
profile_name = "DeviceN";
snprintf(selector, sizeof(selector), "%s..", profile_name);
profile_id = _ppdHashName(selector);
profile = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (!profile)
{
cupsdLogMessage(CUPSD_LOG_ERROR,
"Unable to allocate memory for color profile.");
CFRelease(profiles);
ppdClose(ppd);
return;
}
apple_init_profile(ppd, NULL, profile, profile_id, cm_choice->choice,
cm_choice->text, NULL);
dict_key = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
CFSTR("%u"), profile_id);
if (dict_key)
{
CFDictionarySetValue(profiles, dict_key, profile);
CFRelease(dict_key);
}
CFRelease(profile);
if (cm_choice->marked)
default_profile_id = profile_id;
}
}
else
{
/*
* Use the default colorspace...
*/
attr = ppdFindAttr(ppd, "DefaultColorSpace", NULL);
num_profiles = (attr && ppd->colorspace == PPD_CS_GRAY) ? 1 : 2;
/*
* Add the grayscale profile first. We always have a grayscale profile.
*/
profile = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (!profile)
{
cupsdLogMessage(CUPSD_LOG_ERROR,
"Unable to allocate memory for color profile.");
CFRelease(profiles);
ppdClose(ppd);
return;
}
profile_id = _ppdHashName("Gray..");
apple_init_profile(ppd, NULL, profile, profile_id, "Gray", "Gray", NULL);
dict_key = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%u"),
profile_id);
if (dict_key)
{
CFDictionarySetValue(profiles, dict_key, profile);
CFRelease(dict_key);
}
CFRelease(profile);
/*
* Then add the RGB/CMYK/DeviceN color profile...
*/
profile = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (!profile)
{
cupsdLogMessage(CUPSD_LOG_ERROR,
"Unable to allocate memory for color profile.");
CFRelease(profiles);
ppdClose(ppd);
return;
}
switch (ppd->colorspace)
{
default :
case PPD_CS_RGB :
case PPD_CS_CMY :
profile_id = _ppdHashName("RGB..");
apple_init_profile(ppd, NULL, profile, profile_id, "RGB", "RGB",
NULL);
break;
case PPD_CS_RGBK :
case PPD_CS_CMYK :
profile_id = _ppdHashName("CMYK..");
apple_init_profile(ppd, NULL, profile, profile_id, "CMYK", "CMYK",
NULL);
break;
case PPD_CS_GRAY :
if (attr)
break;
case PPD_CS_N :
profile_id = _ppdHashName("DeviceN..");
apple_init_profile(ppd, NULL, profile, profile_id, "DeviceN",
"DeviceN", NULL);
break;
}
if (CFDictionaryGetCount(profile) > 0)
{
dict_key = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
CFSTR("%u"), profile_id);
if (dict_key)
{
CFDictionarySetValue(profiles, dict_key, profile);
CFRelease(dict_key);
}
}
CFRelease(profile);
}
if (num_profiles > 0)
{
/*
* Make sure we have a default profile ID...
*/
if (!default_profile_id)
default_profile_id = profile_id; /* Last profile */
dict_key = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%u"),
default_profile_id);
if (dict_key)
{
CFDictionarySetValue(profiles, kColorSyncDeviceDefaultProfileID,
dict_key);
CFRelease(dict_key);
}
/*
* Get the device ID hash and pathelogical name dictionary.
*/
cupsdLogMessage(CUPSD_LOG_INFO, "Registering ICC color profiles for \"%s\"",
p->name);
device_id = _ppdHashName(p->name);
device_name = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
printer_name = CFStringCreateWithCString(kCFAllocatorDefault,
p->name, kCFStringEncodingUTF8);
if (device_name && printer_name)
{
/*
* Register the device with ColorSync...
*/
CFTypeRef deviceDictKeys[] =
{ /* Device keys */
kColorSyncDeviceDescriptions,
kColorSyncFactoryProfiles,
kColorSyncDeviceUserScope,
kColorSyncDeviceHostScope
};
CFTypeRef deviceDictVals[] =
{ /* Device values */
device_name,
profiles,
kCFPreferencesAnyUser,
kCFPreferencesCurrentHost
};
CFDictionaryRef deviceDict; /* Device dictionary */
CFUUIDRef deviceUUID; /* Device UUID */
CFDictionarySetValue(device_name, CFSTR("en_US"), printer_name);
deviceDict = CFDictionaryCreate(kCFAllocatorDefault,
(const void **)deviceDictKeys,
(const void **)deviceDictVals,
sizeof(deviceDictKeys) /
sizeof(deviceDictKeys[0]),
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
deviceUUID = ColorSyncCreateUUIDFromUInt32(device_id);
if (!deviceDict || !deviceUUID ||
!ColorSyncRegisterDevice(kColorSyncPrinterDeviceClass, deviceUUID,
deviceDict))
error = 1001;
if (deviceUUID)
CFRelease(deviceUUID);
if (deviceDict)
CFRelease(deviceDict);
}
else
error = 1000;
/*
* Clean up...
*/
if (error != noErr)
cupsdLogMessage(CUPSD_LOG_ERROR,
"Unable to register ICC color profiles for \"%s\": %d",
p->name, (int)error);
if (printer_name)
CFRelease(printer_name);
if (device_name)
CFRelease(device_name);
}
/*
* Free any memory we used...
*/
CFRelease(profiles);
ppdClose(ppd);
}
/*
* 'apple_unregister_profiles()' - Remove color profiles for the specified
* printer.
*/
static void
apple_unregister_profiles(
cupsd_printer_t *p) /* I - Printer */
{
/*
* Make sure ColorSync is available...
*/
if (&ColorSyncUnregisterDevice != NULL)
{
CFUUIDRef deviceUUID; /* Device UUID */
deviceUUID = ColorSyncCreateUUIDFromUInt32(_ppdHashName(p->name));
if (deviceUUID)
{
ColorSyncUnregisterDevice(kColorSyncPrinterDeviceClass, deviceUUID);
CFRelease(deviceUUID);
}
}
}
#elif defined(HAVE_DBUS)
/*
* 'colord_create_device()' - Create a device and register profiles.
*/
static void
colord_create_device(
cupsd_printer_t *p, /* I - Printer */
ppd_file_t *ppd, /* I - PPD file */
cups_array_t *profiles, /* I - Profiles array */
const char *colorspace, /* I - Device colorspace, e.g. 'rgb' */
char **format, /* I - Device qualifier format */
const char *relation, /* I - Profile relation, either 'soft'
or 'hard' */
const char *scope) /* I - The scope of the device, e.g.
'normal', 'temp' or 'disk' */
{
DBusMessage *message = NULL; /* D-Bus request */
DBusMessage *reply = NULL; /* D-Bus reply */
DBusMessageIter args; /* D-Bus method arguments */
DBusMessageIter dict; /* D-Bus method arguments */
DBusError error; /* D-Bus error */
const char *device_path; /* Device object path */
const char *profile_path; /* Profile path */
char *default_profile_path = NULL;
/* Default profile path */
char device_id[1024]; /* Device ID as understood by colord */
char format_str[1024]; /* Qualifier format as a string */
/*
* Create the device...
*/
snprintf(device_id, sizeof(device_id), "cups-%s", p->name);
device_path = device_id;
message = dbus_message_new_method_call(COLORD_DBUS_SERVICE,
COLORD_DBUS_PATH,
COLORD_DBUS_INTERFACE,
"CreateDevice");
dbus_message_iter_init_append(message, &args);
dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &device_path);
dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &scope);
snprintf(format_str, sizeof(format_str), "%s.%s.%s", format[0], format[1],
format[2]);
dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY, "{ss}", &dict);
colord_dict_add_strings(&dict, "Colorspace", colorspace);
colord_dict_add_strings(&dict, "Mode", COLORD_MODE_PHYSICAL);
if (ppd->manufacturer)
colord_dict_add_strings(&dict, "Vendor", ppd->manufacturer);
if (ppd->modelname)
colord_dict_add_strings(&dict, "Model", ppd->modelname);
if (p->sanitized_device_uri)
colord_dict_add_strings(&dict, "Serial", p->sanitized_device_uri);
colord_dict_add_strings(&dict, "Format", format_str);
colord_dict_add_strings(&dict, "Kind", COLORD_KIND_PRINTER);
dbus_message_iter_close_container(&args, &dict);
/*
* Send the CreateDevice request synchronously...
*/
dbus_error_init(&error);
cupsdLogMessage(CUPSD_LOG_DEBUG, "Calling CreateDevice(%s,%s)", device_id,
scope);
reply = dbus_connection_send_with_reply_and_block(colord_con, message,
COLORD_DBUS_TIMEOUT,
&error);
if (!reply)
{
cupsdLogMessage(CUPSD_LOG_WARN, "CreateDevice failed: %s:%s", error.name,
error.message);
dbus_error_free(&error);
goto out;
}
/*
* Get reply data...
*/
dbus_message_iter_init(reply, &args);
if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH)
{
cupsdLogMessage(CUPSD_LOG_WARN,
"CreateDevice failed: Incorrect reply type.");
goto out;
}
dbus_message_iter_get_basic(&args, &device_path);
cupsdLogMessage(CUPSD_LOG_DEBUG, "Created device \"%s\".", device_path);
/*
* Add profiles...
*/
for (profile_path = cupsArrayFirst(profiles);
profile_path;
profile_path = cupsArrayNext(profiles))
{
colord_device_add_profile(device_path, profile_path, relation);
}
out:
if (default_profile_path)
free(default_profile_path);
if (message)
dbus_message_unref(message);
if (reply)
dbus_message_unref(reply);
}
/*
* 'colord_create_profile()' - Create a color profile for a printer.
*/
static void
colord_create_profile(
cups_array_t *profiles, /* I - Profiles array */
const char *printer_name, /* I - Printer name */
const char *qualifier, /* I - Profile qualifier */
const char *colorspace, /* I - Profile colorspace */
char **format, /* I - Profile qualifier format */
const char *iccfile, /* I - ICC filename */
const char *scope) /* I - The scope of the profile, e.g.
'normal', 'temp' or 'disk' */
{
DBusMessage *message = NULL; /* D-Bus request */
DBusMessage *reply = NULL; /* D-Bus reply */
DBusMessageIter args; /* D-Bus method arguments */
DBusMessageIter dict; /* D-Bus method arguments */
DBusError error; /* D-Bus error */
char *idstr; /* Profile ID string */
size_t idstrlen; /* Profile ID allocated length */
const char *profile_path; /* Device object path */
char format_str[1024]; /* Qualifier format as a string */
/*
* Create the profile...
*/
message = dbus_message_new_method_call(COLORD_DBUS_SERVICE,
COLORD_DBUS_PATH,
COLORD_DBUS_INTERFACE,
"CreateProfile");
idstrlen = strlen(printer_name) + 1 + strlen(qualifier) + 1;
if ((idstr = malloc(idstrlen)) == NULL)
goto out;
snprintf(idstr, idstrlen, "%s-%s", printer_name, qualifier);
cupsdLogMessage(CUPSD_LOG_DEBUG, "Using profile ID \"%s\".", idstr);
dbus_message_iter_init_append(message, &args);
dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &idstr);
dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &scope);
snprintf(format_str, sizeof(format_str), "%s.%s.%s", format[0], format[1],
format[2]);
dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY, "{ss}", &dict);
colord_dict_add_strings(&dict, "Qualifier", qualifier);
colord_dict_add_strings(&dict, "Format", format_str);
colord_dict_add_strings(&dict, "Colorspace", colorspace);
if (iccfile)
colord_dict_add_strings(&dict, "Filename", iccfile);
dbus_message_iter_close_container(&args, &dict);
/*
* Send the CreateProfile request synchronously...
*/
dbus_error_init(&error);
cupsdLogMessage(CUPSD_LOG_DEBUG, "Calling CreateProfile(%s,%s)", idstr,
scope);
reply = dbus_connection_send_with_reply_and_block(colord_con, message,
COLORD_DBUS_TIMEOUT,
&error);
if (!reply)
{
cupsdLogMessage(CUPSD_LOG_WARN, "CreateProfile failed: %s:%s", error.name,
error.message);
dbus_error_free(&error);
goto out;
}
/*
* Get reply data...
*/
dbus_message_iter_init(reply, &args);
if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH)
{
cupsdLogMessage(CUPSD_LOG_WARN,
"CreateProfile failed: Incorrect reply type.");
goto out;
}
dbus_message_iter_get_basic(&args, &profile_path);
cupsdLogMessage(CUPSD_LOG_DEBUG, "Created profile \"%s\".", profile_path);
cupsArrayAdd(profiles, strdup(profile_path));
out:
if (message)
dbus_message_unref(message);
if (reply)
dbus_message_unref(reply);
if (idstr)
free(idstr);
}
/*
* 'colord_delete_device()' - Delete a device
*/
static void
colord_delete_device(
const char *device_id) /* I - Device ID string */
{
DBusMessage *message = NULL; /* D-Bus request */
DBusMessage *reply = NULL; /* D-Bus reply */
DBusMessageIter args; /* D-Bus method arguments */
DBusError error; /* D-Bus error */
char *device_path; /* Device object path */
/*
* Find the device...
*/
if ((device_path = colord_find_device(device_id)) == NULL)
goto out;
/*
* Delete the device...
*/
message = dbus_message_new_method_call(COLORD_DBUS_SERVICE,
COLORD_DBUS_PATH,
COLORD_DBUS_INTERFACE,
"DeleteDevice");
dbus_message_iter_init_append(message, &args);
dbus_message_iter_append_basic(&args, DBUS_TYPE_OBJECT_PATH, &device_path);
/*
* Send the DeleteDevice request synchronously...
*/
dbus_error_init(&error);
cupsdLogMessage(CUPSD_LOG_DEBUG, "Calling DeleteDevice(%s)", device_path);
reply = dbus_connection_send_with_reply_and_block(colord_con, message,
COLORD_DBUS_TIMEOUT,
&error);
if (!reply)
{
cupsdLogMessage(CUPSD_LOG_DEBUG, "DeleteDevice failed: %s:%s", error.name,
error.message);
dbus_error_free(&error);
goto out;
}
out:
if (device_path)
free(device_path);
if (message)
dbus_message_unref(message);
if (reply)
dbus_message_unref(reply);
}
/*
* 'colord_device_add_profile()' - Assign a profile to a device.
*/
static void
colord_device_add_profile(
const char *device_path, /* I - Device object path */
const char *profile_path, /* I - Profile object path */
const char *relation) /* I - Device relation, either
'soft' or 'hard' */
{
DBusMessage *message = NULL; /* D-Bus request */
DBusMessage *reply = NULL; /* D-Bus reply */
DBusMessageIter args; /* D-Bus method arguments */
DBusError error; /* D-Bus error */
message = dbus_message_new_method_call(COLORD_DBUS_SERVICE,
device_path,
COLORD_DBUS_INTERFACE_DEVICE,
"AddProfile");
dbus_message_iter_init_append(message, &args);
dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &relation);
dbus_message_iter_append_basic(&args, DBUS_TYPE_OBJECT_PATH, &profile_path);
cupsdLogMessage(CUPSD_LOG_DEBUG, "Calling %s:AddProfile(%s) [%s]",
device_path, profile_path, relation);
/*
* Send the AddProfile request synchronously...
*/
dbus_error_init(&error);
reply = dbus_connection_send_with_reply_and_block(colord_con, message,
COLORD_DBUS_TIMEOUT,
&error);
if (!reply)
{
cupsdLogMessage(CUPSD_LOG_WARN, "AddProfile failed: %s:%s", error.name,
error.message);
dbus_error_free(&error);
goto out;
}
out:
if (message)
dbus_message_unref(message);
if (reply)
dbus_message_unref(reply);
}
/*
* 'colord_dict_add_strings()' - Add two strings to a dictionary.
*/
static void
colord_dict_add_strings(
DBusMessageIter *dict, /* I - Dictionary */
const char *key, /* I - Key string */
const char *value) /* I - Value string */
{
DBusMessageIter entry; /* Entry to add */
dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry);
dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &value);
dbus_message_iter_close_container(dict, &entry);
}
/*
* 'colord_find_device()' - Finds a device
*/
static char * /* O - Device path or NULL */
colord_find_device(
const char *device_id) /* I - Device ID string */
{
DBusMessage *message = NULL; /* D-Bus request */
DBusMessage *reply = NULL; /* D-Bus reply */
DBusMessageIter args; /* D-Bus method arguments */
DBusError error; /* D-Bus error */
const char *device_path_tmp; /* Device object path */
char *device_path = NULL; /* Device object path */
message = dbus_message_new_method_call(COLORD_DBUS_SERVICE,
COLORD_DBUS_PATH,
COLORD_DBUS_INTERFACE,
"FindDeviceById");
dbus_message_iter_init_append(message, &args);
dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &device_id);
/*
* Send the FindDeviceById request synchronously...
*/
dbus_error_init(&error);
cupsdLogMessage(CUPSD_LOG_DEBUG, "Calling FindDeviceById(%s)", device_id);
reply = dbus_connection_send_with_reply_and_block(colord_con, message,
COLORD_DBUS_TIMEOUT,
&error);
if (!reply)
{
cupsdLogMessage(CUPSD_LOG_DEBUG, "FindDeviceById failed: %s:%s",
error.name, error.message);
dbus_error_free(&error);
goto out;
}
/*
* Get reply data...
*/
dbus_message_iter_init(reply, &args);
if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH)
{
cupsdLogMessage(CUPSD_LOG_WARN,
"FindDeviceById failed: Incorrect reply type.");
goto out;
}
dbus_message_iter_get_basic(&args, &device_path_tmp);
if (device_path_tmp)
device_path = strdup(device_path_tmp);
out:
if (message)
dbus_message_unref(message);
if (reply)
dbus_message_unref(reply);
return (device_path);
}
/*
* 'colord_get_qualifier_format()' - Get the qualifier format.
*
* Note: Returns a value of "ColorSpace.MediaType.Resolution" by default.
*/
static void
colord_get_qualifier_format(
ppd_file_t *ppd, /* I - PPD file data */
char *format[3]) /* I - Format tuple */
{
const char *tmp; /* Temporary string */
ppd_attr_t *attr; /* Profile attributes */
/*
* Get 1st section...
*/
if ((attr = ppdFindAttr(ppd, "cupsICCQualifier1", NULL)) != NULL)
tmp = attr->value;
else if (ppdFindAttr(ppd, "DefaultColorModel", NULL))
tmp = "ColorModel";
else if (ppdFindAttr(ppd, "DefaultColorSpace", NULL))
tmp = "ColorSpace";
else
tmp = "";
format[0] = strdup(tmp);
/*
* Get 2nd section...
*/
if ((attr = ppdFindAttr(ppd, "cupsICCQualifier2", NULL)) != NULL)
tmp = attr->value;
else
tmp = "MediaType";
format[1] = strdup(tmp);
/*
* Get 3rd section...
*/
if ((attr = ppdFindAttr(ppd, "cupsICCQualifier3", NULL)) != NULL)
tmp = attr->value;
else
tmp = "Resolution";
format[2] = strdup(tmp);
}
/*
* 'colord_register_printer()' - Register profiles for a printer.
*/
static void
colord_register_printer(
cupsd_printer_t *p) /* I - printer */
{
char ppdfile[1024], /* PPD filename */
iccfile[1024]; /* ICC filename */
ppd_file_t *ppd; /* PPD file */
cups_array_t *profiles; /* Profile paths array */
ppd_attr_t *attr; /* Profile attributes */
const char *device_colorspace; /* Device colorspace */
char *format[3]; /* Qualifier format tuple */
/*
* Ensure we have a D-Bus connection...
*/
if (!colord_con)
return;
/*
* Try opening the PPD file for this printer...
*/
snprintf(ppdfile, sizeof(ppdfile), "%s/ppd/%s.ppd", ServerRoot, p->name);
if ((ppd = _ppdOpenFile(ppdfile, _PPD_LOCALIZATION_ICC_PROFILES)) == NULL)
return;
/*
* Find out the qualifier format
*/
colord_get_qualifier_format(ppd, format);
/*
* See if we have any embedded profiles...
*/
profiles = cupsArrayNew3(NULL, NULL, NULL, 0, (cups_acopy_func_t)strdup,
(cups_afree_func_t)free);
for (attr = ppdFindAttr(ppd, "cupsICCProfile", NULL);
attr;
attr = ppdFindNextAttr(ppd, "cupsICCProfile", NULL))
if (attr->spec[0] && attr->value && attr->value[0])
{
if (attr->value[0] != '/')
snprintf(iccfile, sizeof(iccfile), "%s/profiles/%s", DataDir,
attr->value);
else
strlcpy(iccfile, attr->value, sizeof(iccfile));
if (_cupsFileCheck(iccfile, _CUPS_FILE_CHECK_FILE, !RunUser,
cupsdLogFCMessage, p))
continue;
colord_create_profile(profiles, p->name, attr->spec, COLORD_SPACE_UNKNOWN,
format, iccfile, COLORD_SCOPE_TEMP);
}
/*
* Add the grayscale profile first. We always have a grayscale profile.
*/
colord_create_profile(profiles, p->name, "Gray..", COLORD_SPACE_GRAY,
format, NULL, COLORD_SCOPE_TEMP);
/*
* Then add the RGB/CMYK/DeviceN color profile...
*/
device_colorspace = "unknown";
switch (ppd->colorspace)
{
case PPD_CS_RGB :
case PPD_CS_CMY :
device_colorspace = COLORD_SPACE_RGB;
colord_create_profile(profiles, p->name, "RGB..", COLORD_SPACE_RGB,
format, NULL, COLORD_SCOPE_TEMP);
break;
case PPD_CS_RGBK :
case PPD_CS_CMYK :
device_colorspace = COLORD_SPACE_CMYK;
colord_create_profile(profiles, p->name, "CMYK..", COLORD_SPACE_CMYK,
format, NULL, COLORD_SCOPE_TEMP);
break;
case PPD_CS_GRAY :
device_colorspace = COLORD_SPACE_GRAY;
break;
case PPD_CS_N :
colord_create_profile(profiles, p->name, "DeviceN..",
COLORD_SPACE_UNKNOWN, format, NULL,
COLORD_SCOPE_TEMP);
break;
}
/*
* Register the device with colord.
*/
cupsdLogMessage(CUPSD_LOG_INFO, "Registering ICC color profiles for \"%s\".",
p->name);
colord_create_device(p, ppd, profiles, device_colorspace, format,
COLORD_RELATION_SOFT, COLORD_SCOPE_TEMP);
/*
* Free any memory we used...
*/
cupsArrayDelete(profiles);
free(format[0]);
free(format[1]);
free(format[2]);
ppdClose(ppd);
}
/*
* 'colord_unregister_printer()' - Unregister profiles for a printer.
*/
static void
colord_unregister_printer(
cupsd_printer_t *p) /* I - printer */
{
char device_id[1024]; /* Device ID as understood by colord */
/*
* Ensure we have a D-Bus connection...
*/
if (!colord_con)
return;
/*
* Just delete the device itself, and leave the profiles registered
*/
snprintf(device_id, sizeof(device_id), "cups-%s", p->name);
colord_delete_device(device_id);
}
#endif /* __APPLE__ */