/* * Color management routines for the CUPS scheduler. * * Copyright © 2021-2023 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 #ifdef __APPLE__ # include extern CFUUIDRef ColorSyncCreateUUIDFromUInt32(unsigned id); # include #elif defined(HAVE_DBUS) # include /* * 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 */ 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__ */