/*
 * Color management routines for the CUPS scheduler.
 *
 * Copyright © 2020-2024 by OpenPrinting.
 * 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		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 (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 */
  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, 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__ */
