Blob Blame History Raw
diff --git a/Makefile.am b/Makefile.am
index 03e437e..1f12fbb 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -53,6 +53,15 @@ foomatic_rip_SOURCES = \
 	fileconverter.c \
 	fileconverter.h
 
+if BUILD_DBUS
+foomatic_rip_SOURCES += \
+	colord.c \
+	colord.h
+
+foomatic_rip_CFLAGS = $(DBUS_CFLAGS) -DHAVE_DBUS
+foomatic_rip_LDADD = $(DBUS_LIBS)
+endif
+
 AM_CPPFLAGS = -DCONFIG_PATH='"$(sysconfdir)/foomatic"'
 
 # Masks for trash files which have to be removed before packaging Foomatic
diff --git a/colord.c b/colord.c
new file mode 100644
index 0000000..260b8c7
--- /dev/null
+++ b/colord.c
@@ -0,0 +1,263 @@
+/* colord.c
+ *
+ * Copyright (C) 2011 Richard Hughes <richard@hughsie.com>
+ *
+ * This file is part of foomatic-rip.
+ *
+ * Foomatic-rip is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Foomatic-rip is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* Common routines for accessing the colord CMS framework */
+
+#include <dbus/dbus.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "colord.h"
+
+#define QUAL_COLORSPACE   0
+#define QUAL_MEDIA        1
+#define QUAL_RESOLUTION   2
+#define QUAL_SIZE         3
+
+static char *
+get_filename_for_profile_path (DBusConnection *con,
+                               const char *object_path)
+{
+  char *filename = NULL;
+  const char *interface = "org.freedesktop.ColorManager.Profile";
+  const char *property = "Filename";
+  const char *tmp;
+  DBusError error;
+  DBusMessageIter args;
+  DBusMessage *message = NULL;
+  DBusMessage *reply = NULL;
+  DBusMessageIter sub;
+
+  message = dbus_message_new_method_call("org.freedesktop.ColorManager",
+                 object_path,
+                 "org.freedesktop.DBus.Properties",
+                 "Get");
+
+  dbus_message_iter_init_append(message, &args);
+  dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &interface);
+  dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &property);
+
+  /* send syncronous */
+  dbus_error_init(&error);
+  fprintf(stderr, "DEBUG: Calling %s.Get(%s)\n", interface, property);
+  reply = dbus_connection_send_with_reply_and_block(con,
+                message,
+                -1,
+                &error);
+  if (reply == NULL) {
+    fprintf(stderr, "DEBUG: Failed to send: %s:%s\n",
+           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_VARIANT) {
+    fprintf(stderr, "DEBUG: Incorrect reply type\n");
+    goto out;
+  }
+
+  dbus_message_iter_recurse(&args, &sub);
+  dbus_message_iter_get_basic(&sub, &tmp);
+  filename = strdup(tmp);
+out:
+  if (message != NULL)
+    dbus_message_unref(message);
+  if (reply != NULL)
+    dbus_message_unref(reply);
+  return filename;
+}
+
+static char *
+get_profile_for_device_path (DBusConnection *con,
+                             const char *object_path,
+                             const char **split)
+{
+  char **key = NULL;
+  char *profile = NULL;
+  char str[256];
+  const char *tmp;
+  DBusError error;
+  DBusMessageIter args;
+  DBusMessageIter entry;
+  DBusMessage *message = NULL;
+  DBusMessage *reply = NULL;
+  int i = 0;
+  const int max_keys = 7;
+
+  message = dbus_message_new_method_call("org.freedesktop.ColorManager",
+                                         object_path,
+                                         "org.freedesktop.ColorManager.Device",
+                                         "GetProfileForQualifiers");
+  dbus_message_iter_init_append(message, &args);
+
+  /* create the fallbacks */
+  key = calloc(max_keys + 1, sizeof(char*));
+
+  /* exact match */
+  i = 0;
+  snprintf(str, sizeof(str), "%s.%s.%s",
+           split[QUAL_COLORSPACE],
+           split[QUAL_MEDIA],
+           split[QUAL_RESOLUTION]);
+  key[i++] = strdup(str);
+  snprintf(str, sizeof(str), "%s.%s.*",
+           split[QUAL_COLORSPACE],
+           split[QUAL_MEDIA]);
+  key[i++] = strdup(str);
+  snprintf(str, sizeof(str), "%s.*.%s",
+           split[QUAL_COLORSPACE],
+           split[QUAL_RESOLUTION]);
+  key[i++] = strdup(str);
+  snprintf(str, sizeof(str), "%s.*.*",
+           split[QUAL_COLORSPACE]);
+  key[i++] = strdup(str);
+  key[i++] = strdup("*");
+  dbus_message_iter_open_container(&args,
+                                   DBUS_TYPE_ARRAY,
+                                   "s",
+                                   &entry);
+  for (i=0; key[i] != NULL; i++) {
+    dbus_message_iter_append_basic(&entry,
+                                   DBUS_TYPE_STRING,
+                                   &key[i]);
+  }
+  dbus_message_iter_close_container(&args, &entry);
+
+  /* send syncronous */
+  dbus_error_init(&error);
+  fprintf(stderr, "DEBUG: Calling GetProfileForQualifiers(%s...)\n", key[0]);
+  reply = dbus_connection_send_with_reply_and_block(con,
+                                                    message,
+                                                    -1,
+                                                    &error);
+  if (reply == NULL) {
+    fprintf(stderr, "DEBUG: Failed to send: %s:%s\n",
+           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) {
+    fprintf(stderr, "DEBUG: Incorrect reply type\n");
+    goto out;
+  }
+  dbus_message_iter_get_basic(&args, &tmp);
+  fprintf(stderr, "DEBUG: Found profile %s\n", tmp);
+
+  /* get filename */
+  profile = get_filename_for_profile_path(con, tmp);
+
+out:
+  if (message != NULL)
+    dbus_message_unref(message);
+  if (reply != NULL)
+    dbus_message_unref(reply);
+  if (key != NULL) {
+    for (i=0; i < max_keys; i++)
+      free(key[i]);
+    free(key);
+  }
+  return profile;
+}
+
+static char *
+get_profile_for_device_id (DBusConnection *con,
+                           const char *device_id,
+                           const char **qualifier_tuple)
+{
+  char *profile = NULL;
+  const char *device_path_tmp;
+  DBusError error;
+  DBusMessageIter args;
+  DBusMessage *message = NULL;
+  DBusMessage *reply = NULL;
+
+  message = dbus_message_new_method_call("org.freedesktop.ColorManager",
+                                         "/org/freedesktop/ColorManager",
+                                         "org.freedesktop.ColorManager",
+                                         "FindDeviceById");
+  dbus_message_iter_init_append(message, &args);
+  dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &device_id);
+
+  /* send syncronous */
+  dbus_error_init(&error);
+  fprintf(stderr, "DEBUG: Calling FindDeviceById(%s)\n", device_id);
+  reply = dbus_connection_send_with_reply_and_block(con,
+                message,
+                -1,
+                &error);
+  if (reply == NULL) {
+    fprintf(stderr, "DEBUG: Failed to send: %s:%s\n",
+            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) {
+    fprintf(stderr, "DEBUG: Incorrect reply type\n");
+    goto out;
+  }
+  dbus_message_iter_get_basic(&args, &device_path_tmp);
+  fprintf(stderr, "DEBUG: Found device %s\n", device_path_tmp);
+  profile = get_profile_for_device_path(con, device_path_tmp, qualifier_tuple);
+out:
+  if (message != NULL)
+    dbus_message_unref(message);
+  if (reply != NULL)
+    dbus_message_unref(reply);
+  return profile;
+}
+
+char *
+colord_get_profile_for_device_id (const char *device_id,
+                              const char **qualifier_tuple)
+{
+  DBusConnection *con;
+  char *filename = NULL;
+
+  /* connect to system bus */
+  con = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+  if (con == NULL) {
+    fprintf(stderr, "ERROR: Failed to connect to system bus\n");
+    goto out;
+  }
+
+  /* get the best profile for the device */
+  filename = get_profile_for_device_id (con, device_id, qualifier_tuple);
+  if (filename == NULL) {
+    fprintf(stderr, "DEBUG: Failed to get profile filename!\n");
+    goto out;
+  }
+  fprintf(stderr, "DEBUG: Use profile filename: '%s'\n", filename);
+out:
+  if (con != NULL)
+    dbus_connection_unref(con);
+  return filename;
+}
diff --git a/colord.h b/colord.h
new file mode 100644
index 0000000..2ea98bc
--- /dev/null
+++ b/colord.h
@@ -0,0 +1,24 @@
+/* colord.h
+ *
+ * Copyright (C) 2011 Richard Hughes <richard@hughsie.com>
+ *
+ * This file is part of foomatic-rip.
+ *
+ * Foomatic-rip is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Foomatic-rip is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+char   *colord_get_profile_for_device_id  (const char *device_id,
+                                           const char **qualifier_tuple);
diff --git a/configure.ac b/configure.ac
index 96a9e21..d33726d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -16,6 +16,9 @@ AC_PROG_INSTALL
 AC_PROG_LN_S
 AC_PROG_MAKE_SET
 
+# Allows per-target compilation flags
+AM_PROG_CC_C_O
+
 # Checks for libraries.
 AC_CHECK_LIB(m, roundf)
 
@@ -119,6 +122,14 @@ AC_ARG_WITH(file-converter,[  --with-file-converter=[a2ps|enscript|mpage]
 AC_MSG_RESULT([file-converter: $FILECONVERTER])
 AC_SUBST(FILECONVERTER)
 
+# Use DBUS
+AC_ARG_ENABLE(dbus, AS_HELP_STRING([--enable-dbus],[enable DBus CMS code]),
+	      enable_dbus=$enableval,enable_dbus=yes)
+AM_CONDITIONAL(BUILD_DBUS, test x$enable_dbus = xyes)
+if test x$enable_dbus = xyes; then
+	PKG_CHECK_MODULES(DBUS, dbus-1)
+fi
+
 if test "${NOCONVERTERCHECK}" = "" -a "${A2PS}" = "" -a "${ENSCRIPT}" = "" -a "${MPAGE}" = "" -a "${TEXTTOPS}" = "" ; then
 	AC_MSG_ERROR([cannot find a2ps, enscript, mpage, or CUPS' texttops.  You need to have at least one installed]);
 fi
diff --git a/foomaticrip.c b/foomaticrip.c
index 41a7577..6065768 100644
--- a/foomaticrip.c
+++ b/foomaticrip.c
@@ -45,6 +45,9 @@
 #include <signal.h>
 #include <pwd.h>
 
+#ifdef HAVE_DBUS
+  #include "colord.h"
+#endif
 
 /* Logging */
 FILE* logh = NULL;
@@ -1477,13 +1480,45 @@ int main(int argc, char** argv)
                     strlcat(pstoraster, " 0 '' '' 0 '%X'", 256);
                     break;
                 }
+                /* gstoraster is the new name for pstoraster */
+                strlcat(tmp, "/gstoraster", 1024);
+                if (access(tmp, X_OK) == 0) {
+                    havepstoraster = 1;
+                    strlcpy(pstoraster, tmp, 256);
+                    strlcat(pstoraster, " 0 '' '' 0 '%X'", 256);
+                    break;
+                }
             }
             if (!havepstoraster) {
-                strcpy(pstoraster, "gs -dQUIET -dDEBUG -dPARANOIDSAFER -dNOPAUSE -dBATCH -dNOMEDIAATTRS -sDEVICE=cups -sOutputFile=-%W -");
+                const char **qualifier = NULL;
+                const char *icc_profile = NULL;
+
+                qualifier = get_ppd_qualifier();
+                _log("INFO: Using qualifer: '%s.%s.%s'\n",
+                      qualifier[0], qualifier[1], qualifier[2]);
+
+                /* ask colord for the profile */
+                icc_profile = colord_get_profile_for_device_id ((const char *) getenv("PRINTER"),
+                                                                qualifier);
+
+                /* fall back to PPD */
+                if (icc_profile == NULL) {
+                  _log("INFO: need to look in PPD for matching qualifer\n");
+                  icc_profile = get_icc_profile_for_qualifier(qualifier);
+                }
+
+                if (icc_profile != NULL)
+                  snprintf(cmd, sizeof(cmd),
+                           "-sOutputICCProfile='%s'", icc_profile);
+                else
+                  cmd[0] = '\0';
+
+                snprintf(pstoraster, sizeof(pstoraster), "gs -dQUIET -dDEBUG -dPARANOIDSAFER -dNOPAUSE -dBATCH -dNOMEDIAATTRS -sDEVICE=cups %s -sOutputFile=-%W -", cmd);
             }
 
             /* build Ghostscript/CUPS driver command line */
             snprintf(cmd, 1024, "%s | %s", pstoraster, cupsfilter);
+            _log("INFO: Using command line: %s\n", cmd);
 
             /* Set environment variables */
             setenv("PPD", job->ppdfile, 1);
diff --git a/options.c b/options.c
index 9a4b116..662cedd 100644
--- a/options.c
+++ b/options.c
@@ -31,6 +31,11 @@
 #include <string.h>
 #include <math.h>
 
+/* qualifier -> filename mapping entry */
+typedef struct icc_mapping_entry_s {
+    char *qualifier;
+    char *filename;
+} icc_mapping_entry_t;
 
 /* Values from foomatic keywords in the ppd file */
 char printer_model [256];
@@ -70,6 +75,8 @@ dstr_t *setupprepend;
 dstr_t *pagesetupprepend;
 
 
+list_t *qualifier_data = NULL;
+char **qualifier = NULL;
 
 option_t *optionlist = NULL;
 option_t *optionlist_sorted_by_order = NULL;
@@ -78,7 +85,42 @@ int optionset_alloc, optionset_count;
 char **optionsets;
 
 
+const char * get_icc_profile_for_qualifier(const char **qualifier)
+{
+    char tmp[1024];
+    char *profile = NULL;
+    listitem_t *i;
+    icc_mapping_entry_t *entry;
+
+    /* no data */
+    if (qualifier_data == NULL)
+        goto out;
+
+    /* search list for qualifier */
+    snprintf(tmp, sizeof(tmp), "%s.%s.%s",
+             qualifier[0], qualifier[1], qualifier[2]);
+    for (i = qualifier_data->first; i != NULL; i = i->next) {
+        entry = (icc_mapping_entry_t *) i->data;
+        if (strcmp(entry->qualifier, tmp) == 0) {
+            profile = entry->filename;
+            break;
+        }
+    }
+out:
+    return profile;
+}
 
+/* a selector is a general tri-dotted specification.
+ * The 2nd and 3rd elements of the qualifier are optionally modified by
+ * cupsICCQualifier2 and cupsICCQualifier3:
+ *
+ * [Colorspace].[{cupsICCQualifier2}].[{cupsICCQualifier3}]
+ */
+const char **
+get_ppd_qualifier ()
+{
+  return (const char**) qualifier;
+}
 
 const char * type_name(int type)
 {
@@ -239,6 +281,8 @@ void options_free()
 {
     option_t *opt;
     int i;
+    listitem_t *item;
+    icc_mapping_entry_t *entry;
 
     for (i = 0; i < optionset_count; i++)
         free(optionsets[i]);
@@ -247,6 +291,20 @@ void options_free()
     optionset_alloc = 0;
     optionset_count = 0;
 
+    if (qualifier_data) {
+        for (item = qualifier_data->first; item != NULL; item = item->next) {
+            entry = (icc_mapping_entry_t *) item->data;
+            free(entry->qualifier);
+            free(entry->filename);
+            free(entry);
+        }
+        list_free(qualifier_data);
+    }
+
+    for (i=0; i<3; i++)
+      free(qualifier[i]);
+    free(qualifier);
+
     while (optionlist) {
         opt = optionlist;
         optionlist = optionlist->next;
@@ -1493,6 +1551,9 @@ int optionset_equal(int optset1, int optset2, int exceptPS)
 void read_ppd_file(const char *filename)
 {
     FILE *fh;
+    const char *tmp;
+    char *icc_qual2 = NULL;
+    char *icc_qual3 = NULL;
     char line [256];            /* PPD line length is max 255 (excl. \0) */
     char *p;
     char key[128], name[64], text[64];
@@ -1501,6 +1562,7 @@ void read_ppd_file(const char *filename)
     value_t *val;
     option_t *opt, *current_opt = NULL;
     param_t *param;
+    icc_mapping_entry_t *entry;
 
     fh = fopen(filename, "r");
     if (!fh) {
@@ -1511,6 +1573,7 @@ void read_ppd_file(const char *filename)
 
     dstrassure(value, 256);
 
+    qualifier_data = list_create();
     while (!feof(fh)) {
         fgets(line, 256, fh);
 
@@ -1691,10 +1754,6 @@ void read_ppd_file(const char *filename)
         else if (!prefixcmp(key, "Default")) {
             /* Default<option>: <value> */
 
-            /* TODO *DefaultColorSpace is a keyword and doesn't need to be extraced, does it? */
-            if (!strcmp(key, "DefaultColorSpace"))
-                continue;
-
             opt = assure_option(&key[7]);
             val = option_assure_value(opt, optionset("default"));
             free(val->value);
@@ -1768,6 +1827,21 @@ void read_ppd_file(const char *filename)
             /*  "*FoomaticRIPOptionsEntityMaxLength: <length>" */
             sscanf(value->data, "%d", &optionsentitymaxlen);
         }
+        else if (!strcmp(key, "cupsICCProfile")) {
+            /*  "*cupsICCProfile: <qualifier/Title> <filename>" */
+            entry = calloc(1, sizeof(icc_mapping_entry_t));
+            entry->qualifier = strdup(name);
+            entry->filename = strdup(value->data);
+            list_append (qualifier_data, entry);
+        }
+        else if (!strcmp(key, "cupsICCQualifier2")) {
+            /*  "*cupsICCQualifier2: <value>" */
+            icc_qual2 = strdup(value->data);
+        }
+        else if (!strcmp(key, "cupsICCQualifier3")) {
+            /*  "*cupsICCQualifier3: <value>" */
+            icc_qual3 = strdup(value->data);
+        }
     }
 
     fclose(fh);
@@ -1786,6 +1860,36 @@ void read_ppd_file(const char *filename)
                defined in the PPD file */
             option_set_value(opt, optionset("default"), opt->choicelist->value);
     }
+
+    /* create qualifier for this PPD */
+    qualifier = calloc(4, sizeof(char*));
+
+    /* get colorspace */
+    tmp = option_get_value(find_option("ColorSpace"), optionset("default"));
+    if (tmp == NULL)
+      tmp = option_get_value(find_option("ColorModel"), optionset("default"));
+    if (tmp == NULL)
+      tmp = "";
+    qualifier[0] = strdup(tmp);
+
+    /* get selector2 */
+    if (icc_qual2 == NULL)
+        icc_qual2 = strdup("MediaType");
+    tmp = option_get_value(find_option(icc_qual2), optionset("default"));
+    if (tmp == NULL)
+      tmp = "";
+    qualifier[1] = strdup(tmp);
+
+    /* get selectors */
+    if (icc_qual3 == NULL)
+        icc_qual3 = strdup("Resolution");
+    tmp = option_get_value(find_option(icc_qual3), optionset("default"));
+    if (tmp == NULL)
+      tmp = "";
+    qualifier[2] = strdup(tmp);
+
+    free (icc_qual2);
+    free (icc_qual3);
 }
 
 int ppd_supports_pdf()
diff --git a/options.h b/options.h
index db7c3e9..242e8f2 100644
--- a/options.h
+++ b/options.h
@@ -177,6 +177,8 @@ void append_page_setup_section(dstr_t *str, int optset, int comments);
 int build_commandline(int optset, dstr_t *cmdline, int pdfcmdline);
 
 void set_options_for_page(int optset, int page);
+const char *get_icc_profile_for_qualifier(const char **qualifier);
+const char **get_ppd_qualifier(void);
 
 #endif