diff options
Diffstat (limited to 'modules/printbackends/cups')
-rw-r--r-- | modules/printbackends/cups/Makefile.am | 34 | ||||
-rw-r--r-- | modules/printbackends/cups/gtkcupsutils.c | 922 | ||||
-rw-r--r-- | modules/printbackends/cups/gtkcupsutils.h | 125 | ||||
-rw-r--r-- | modules/printbackends/cups/gtkprintbackendcups.c | 2629 | ||||
-rw-r--r-- | modules/printbackends/cups/gtkprintbackendcups.h | 42 | ||||
-rw-r--r-- | modules/printbackends/cups/gtkprintercups.c | 125 | ||||
-rw-r--r-- | modules/printbackends/cups/gtkprintercups.h | 70 |
7 files changed, 3947 insertions, 0 deletions
diff --git a/modules/printbackends/cups/Makefile.am b/modules/printbackends/cups/Makefile.am new file mode 100644 index 0000000000..0174598256 --- /dev/null +++ b/modules/printbackends/cups/Makefile.am @@ -0,0 +1,34 @@ +if OS_WIN32 +no_undefined = -no-undefined +endif + +INCLUDES = \ + -I$(top_srcdir) \ + -I$(top_srcdir)/gtk \ + -I$(top_builddir)/gtk \ + -I$(top_srcdir)/gdk \ + -I$(top_builddir)/gdk \ + $(CUPS_CFLAGS) \ + -DGTK_PRINT_BACKEND_ENABLE_UNSUPPORTED \ + $(GTK_DEP_CFLAGS) + +LDADDS = \ + $(GTK_DEP_LIBS) \ + $(top_builddir)/gtk/$(gtktargetlib) + +backenddir = $(libdir)/gtk-2.0/$(GTK_BINARY_VERSION)/printbackends + +backend_LTLIBRARIES = libprintbackend-cups.la + +libprintbackend_cups_la_SOURCES = \ + gtkprintbackendcups.c \ + gtkprintercups.c \ + gtkcupsutils.c + +noinst_HEADERS = \ + gtkprintbackendcups.h \ + gtkprintercups.h \ + gtkcupsutils.h + +libprintbackend_cups_la_LDFLAGS = -avoid-version -module $(no_undefined) +libprintbackend_cups_la_LIBADD = $(LDADDS) $(CUPS_LIBS) diff --git a/modules/printbackends/cups/gtkcupsutils.c b/modules/printbackends/cups/gtkcupsutils.c new file mode 100644 index 0000000000..458e1c4219 --- /dev/null +++ b/modules/printbackends/cups/gtkcupsutils.c @@ -0,0 +1,922 @@ +/* GTK - The GIMP Toolkit + * gtkcupsutils.h: Statemachine implementation of POST and GET + * cup calls which can be used to create a non-blocking cups API + * Copyright (C) 2003, Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser 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. + */ + +#include "gtkcupsutils.h" + +#include <errno.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <time.h> + +typedef void (*GtkCupsRequestStateFunc) (GtkCupsRequest *request); + +static void _connect (GtkCupsRequest *request); +static void _post_send (GtkCupsRequest *request); +static void _post_write_request (GtkCupsRequest *request); +static void _post_write_data (GtkCupsRequest *request); +static void _post_check (GtkCupsRequest *request); +static void _post_read_response (GtkCupsRequest *request); + +static void _get_send (GtkCupsRequest *request); +static void _get_check (GtkCupsRequest *request); +static void _get_read_data (GtkCupsRequest *request); + +struct _GtkCupsResult +{ + gchar *error_msg; + ipp_t *ipp_response; + + guint is_error : 1; + guint is_ipp_response : 1; +}; + + +#define _GTK_CUPS_MAX_ATTEMPTS 10 +#define _GTK_CUPS_MAX_CHUNK_SIZE 8192 + +GtkCupsRequestStateFunc post_states[] = {_connect, + _post_send, + _post_write_request, + _post_write_data, + _post_check, + _post_read_response}; + +GtkCupsRequestStateFunc get_states[] = {_connect, + _get_send, + _get_check, + _get_read_data}; + +static void +gtk_cups_result_set_error (GtkCupsResult *result, + const char *error_msg, + ...) +{ + va_list args; + + result->is_ipp_response = FALSE; + + result->is_error = TRUE; + + va_start (args, error_msg); + result->error_msg = g_strdup_vprintf (error_msg, args); + va_end (args); +} + +GtkCupsRequest * +gtk_cups_request_new (http_t *connection, + GtkCupsRequestType req_type, + gint operation_id, + gint data_fd, + const char *server, + const char *resource) +{ + GtkCupsRequest *request; + cups_lang_t *language; + + request = g_new0 (GtkCupsRequest, 1); + request->result = g_new0 (GtkCupsResult, 1); + + request->result->error_msg = NULL; + request->result->ipp_response = NULL; + + request->result->is_error = FALSE; + request->result->is_ipp_response = FALSE; + + request->type = req_type; + request->state = GTK_CUPS_REQUEST_START; + + if (server) + request->server = g_strdup (server); + else + request->server = g_strdup (cupsServer()); + + + if (resource) + request->resource = g_strdup (resource); + else + request->resource = g_strdup ("/"); + + if (connection != NULL) + { + request->http = connection; + request->own_http = FALSE; + } + else + { + request->http = NULL; + request->http = httpConnectEncrypt (request->server, ippPort(), cupsEncryption()); + + if (request->http) + httpBlocking (request->http, 0); + + request->own_http = TRUE; + } + + request->last_status = HTTP_CONTINUE; + + request->attempts = 0; + request->data_fd = data_fd; + + request->ipp_request = ippNew(); + request->ipp_request->request.op.operation_id = operation_id; + request->ipp_request->request.op.request_id = 1; + + language = cupsLangDefault (); + + gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_CHARSET, + "attributes-charset", + NULL, "utf-8"); + + gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, + "attributes-natural-language", + NULL, language->language); + + cupsLangFree (language); + + return request; +} + +static void +gtk_cups_result_free (GtkCupsResult *result) +{ + g_free (result->error_msg); + + if (result->ipp_response) + ippDelete (result->ipp_response); + + g_free (result); +} + +void +gtk_cups_request_free (GtkCupsRequest *request) +{ + if (request->own_http) + if (request->http) + httpClose (request->http); + + if (request->ipp_request) + ippDelete (request->ipp_request); + + g_free (request->server); + g_free (request->resource); + + gtk_cups_result_free (request->result); + + g_free (request); +} + +gboolean +gtk_cups_request_read_write (GtkCupsRequest *request) +{ + if (request->type == GTK_CUPS_POST) + post_states[request->state](request); + else if (request->type == GTK_CUPS_GET) + get_states[request->state](request); + + if (request->attempts > _GTK_CUPS_MAX_ATTEMPTS && + request->state != GTK_CUPS_REQUEST_DONE) + { + gtk_cups_result_set_error (request->result, "Too many failed attempts"); + request->state = GTK_CUPS_REQUEST_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + } + + if (request->state == GTK_CUPS_REQUEST_DONE) + { + request->poll_state = GTK_CUPS_HTTP_IDLE; + return TRUE; + } + else + { + return FALSE; + } +} + +GtkCupsPollState +gtk_cups_request_get_poll_state (GtkCupsRequest *request) +{ + return request->poll_state; +} + + + +GtkCupsResult * +gtk_cups_request_get_result (GtkCupsRequest *request) +{ + return request->result; +} + +void +gtk_cups_request_ipp_add_string (GtkCupsRequest *request, + ipp_tag_t group, + ipp_tag_t tag, + const char *name, + const char *charset, + const char *value) +{ + ippAddString (request->ipp_request, + group, + tag, + name, + charset, + value); +} + +typedef struct +{ + const char *name; + ipp_tag_t value_tag; +} ipp_option_t; + +static const ipp_option_t ipp_options[] = + { + { "blackplot", IPP_TAG_BOOLEAN }, + { "brightness", IPP_TAG_INTEGER }, + { "columns", IPP_TAG_INTEGER }, + { "copies", IPP_TAG_INTEGER }, + { "finishings", IPP_TAG_ENUM }, + { "fitplot", IPP_TAG_BOOLEAN }, + { "gamma", IPP_TAG_INTEGER }, + { "hue", IPP_TAG_INTEGER }, + { "job-k-limit", IPP_TAG_INTEGER }, + { "job-page-limit", IPP_TAG_INTEGER }, + { "job-priority", IPP_TAG_INTEGER }, + { "job-quota-period", IPP_TAG_INTEGER }, + { "landscape", IPP_TAG_BOOLEAN }, + { "media", IPP_TAG_KEYWORD }, + { "mirror", IPP_TAG_BOOLEAN }, + { "natural-scaling", IPP_TAG_INTEGER }, + { "number-up", IPP_TAG_INTEGER }, + { "orientation-requested", IPP_TAG_ENUM }, + { "page-bottom", IPP_TAG_INTEGER }, + { "page-left", IPP_TAG_INTEGER }, + { "page-ranges", IPP_TAG_RANGE }, + { "page-right", IPP_TAG_INTEGER }, + { "page-top", IPP_TAG_INTEGER }, + { "penwidth", IPP_TAG_INTEGER }, + { "ppi", IPP_TAG_INTEGER }, + { "prettyprint", IPP_TAG_BOOLEAN }, + { "printer-resolution", IPP_TAG_RESOLUTION }, + { "print-quality", IPP_TAG_ENUM }, + { "saturation", IPP_TAG_INTEGER }, + { "scaling", IPP_TAG_INTEGER }, + { "sides", IPP_TAG_KEYWORD }, + { "wrap", IPP_TAG_BOOLEAN } + }; + + +static ipp_tag_t +_find_option_tag (const gchar *option) +{ + int lower_bound, upper_bound, num_options; + int current_option; + ipp_tag_t result; + + result = IPP_TAG_ZERO; + + lower_bound = 0; + upper_bound = num_options = (int)(sizeof(ipp_options) / sizeof(ipp_options[0])) - 1; + + while (1) + { + int match; + current_option = (int) (((upper_bound - lower_bound) / 2) + lower_bound); + + match = strcasecmp(option, ipp_options[current_option].name); + if (match == 0) + { + result = ipp_options[current_option].value_tag; + return result; + } + else if (match < 0) + { + upper_bound = current_option - 1; + } + else + { + lower_bound = current_option + 1; + } + + if (upper_bound == lower_bound && upper_bound == current_option) + return result; + + if (upper_bound < 0) + return result; + + if (lower_bound > num_options) + return result; + + if (upper_bound < lower_bound) + return result; + } +} + +void +gtk_cups_request_encode_option (GtkCupsRequest *request, + const gchar *option, + const gchar *value) +{ + ipp_tag_t option_tag; + + g_assert (option != NULL); + g_assert (value != NULL); + + option_tag = _find_option_tag (option); + + if (option_tag == IPP_TAG_ZERO) + { + option_tag = IPP_TAG_NAME; + if (strcasecmp (value, "true") == 0 || + strcasecmp (value, "false") == 0) + { + option_tag = IPP_TAG_BOOLEAN; + } + } + + switch (option_tag) + { + case IPP_TAG_INTEGER: + case IPP_TAG_ENUM: + ippAddInteger (request->ipp_request, + IPP_TAG_OPERATION, + option_tag, + option, + strtol (value, NULL, 0)); + break; + + case IPP_TAG_BOOLEAN: + { + char b; + b = 0; + if (!strcasecmp(value, "true") || + !strcasecmp(value, "on") || + !strcasecmp(value, "yes")) + b = 1; + + ippAddBoolean(request->ipp_request, + IPP_TAG_OPERATION, + option, + b); + + break; + } + + case IPP_TAG_RANGE: + { + char *s; + int lower; + int upper; + + if (*value == '-') + { + lower = 1; + s = (char *)value; + } + else + lower = strtol(value, &s, 0); + + if (*s == '-') + { + if (s[1]) + upper = strtol(s + 1, NULL, 0); + else + upper = 2147483647; + } + else + upper = lower; + + ippAddRange (request->ipp_request, + IPP_TAG_OPERATION, + option, + lower, + upper); + + break; + } + + case IPP_TAG_RESOLUTION: + { + char *s; + int xres; + int yres; + ipp_res_t units; + + xres = strtol(value, &s, 0); + + if (*s == 'x') + yres = strtol(s + 1, &s, 0); + else + yres = xres; + + if (strcasecmp(s, "dpc") == 0) + units = IPP_RES_PER_CM; + else + units = IPP_RES_PER_INCH; + + ippAddResolution (request->ipp_request, + IPP_TAG_OPERATION, + option, + units, + xres, + yres); + + break; + } + + default: + ippAddString (request->ipp_request, + IPP_TAG_OPERATION, + option_tag, + option, + NULL, + value); + + break; + } +} + + +static void +_connect (GtkCupsRequest *request) +{ + request->poll_state = GTK_CUPS_HTTP_IDLE; + + if (request->http == NULL) + { + request->http = httpConnectEncrypt (request->server, ippPort(), cupsEncryption()); + + if (request->http == NULL) + request->attempts++; + + if (request->http) + httpBlocking (request->http, 0); + + request->own_http = TRUE; + } + else + { + request->attempts = 0; + request->state++; + + /* we always write to the socket after we get + the connection */ + request->poll_state = GTK_CUPS_HTTP_WRITE; + } +} + +static void +_post_send (GtkCupsRequest *request) +{ + gchar length[255]; + struct stat data_info; + + request->poll_state = GTK_CUPS_HTTP_WRITE; + + if (request->data_fd != 0) + { + fstat (request->data_fd, &data_info); + sprintf (length, "%lu", (unsigned long)ippLength(request->ipp_request) + data_info.st_size); + } + else + { + sprintf (length, "%lu", (unsigned long)ippLength(request->ipp_request)); + } + + httpClearFields(request->http); + httpSetField(request->http, HTTP_FIELD_CONTENT_LENGTH, length); + httpSetField(request->http, HTTP_FIELD_CONTENT_TYPE, "application/ipp"); + httpSetField(request->http, HTTP_FIELD_AUTHORIZATION, request->http->authstring); + + if (httpPost(request->http, request->resource)) + { + if (httpReconnect(request->http)) + { + request->state = GTK_CUPS_POST_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + + gtk_cups_result_set_error (request->result, "Failed Post"); + } + + request->attempts++; + return; + } + + request->attempts = 0; + + request->state = GTK_CUPS_POST_WRITE_REQUEST; + request->ipp_request->state = IPP_IDLE; +} + +static void +_post_write_request (GtkCupsRequest *request) +{ + ipp_state_t ipp_status; + + request->poll_state = GTK_CUPS_HTTP_WRITE; + + ipp_status = ippWrite(request->http, request->ipp_request); + + if (ipp_status == IPP_ERROR) + { + request->state = GTK_CUPS_POST_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + + gtk_cups_result_set_error (request->result, "%s",ippErrorString (cupsLastError ())); + return; + } + + if (ipp_status == IPP_DATA) + { + if (request->data_fd != 0) + request->state = GTK_CUPS_POST_WRITE_DATA; + else + { + request->state = GTK_CUPS_POST_CHECK; + request->poll_state = GTK_CUPS_HTTP_READ; + } + } +} + +static void +_post_write_data (GtkCupsRequest *request) +{ + ssize_t bytes; + char buffer[_GTK_CUPS_MAX_CHUNK_SIZE]; + http_status_t http_status; + + request->poll_state = GTK_CUPS_HTTP_WRITE; + + if (httpCheck (request->http)) + http_status = httpUpdate(request->http); + else + http_status = request->last_status; + + request->last_status = http_status; + + + if (http_status == HTTP_CONTINUE || http_status == HTTP_OK) + { + /* send data */ + bytes = read(request->data_fd, buffer, _GTK_CUPS_MAX_CHUNK_SIZE); + + if (bytes == 0) + { + request->state = GTK_CUPS_POST_CHECK; + request->poll_state = GTK_CUPS_HTTP_READ; + + request->attempts = 0; + return; + } + else if (bytes == -1) + { + request->state = GTK_CUPS_POST_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + + gtk_cups_result_set_error (request->result, "Error reading from cache file: %s", strerror (errno)); + return; + } + + if (httpWrite(request->http, buffer, (int)bytes) < bytes) + { + request->state = GTK_CUPS_POST_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + + gtk_cups_result_set_error (request->result, "Error writting to socket in Post %s", strerror (httpError (request->http))); + return; + } + } + else + { + request->attempts++; + } +} + +static void +_post_check (GtkCupsRequest *request) +{ + http_status_t http_status; + + http_status = request->last_status; + + request->poll_state = GTK_CUPS_HTTP_READ; + + if (http_status == HTTP_CONTINUE) + { + goto again; + } + else if (http_status == HTTP_UNAUTHORIZED) + { + /* TODO: callout for auth */ + g_warning ("NOT IMPLEMENTED: We need to prompt for authorization"); + request->state = GTK_CUPS_POST_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + + gtk_cups_result_set_error (request->result, "Can't prompt for authorization"); + return; + } + else if (http_status == HTTP_ERROR) + { +#ifdef G_OS_WIN32 + if (request->http->error != WSAENETDOWN && + request->http->error != WSAENETUNREACH) +#else + if (request->http->error != ENETDOWN && + request->http->error != ENETUNREACH) +#endif /* G_OS_WIN32 */ + { + request->attempts++; + goto again; + } + else + { + request->state = GTK_CUPS_POST_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + + gtk_cups_result_set_error (request->result, "Unknown HTTP error"); + return; + } + } +/* TODO: detect ssl in configure.ac */ +#if HAVE_SSL + else if (http_status == HTTP_UPGRADE_REQUIRED) + { + /* Flush any error message... */ + httpFlush (request->http); + + /* Reconnect... */ + httpReconnect (request->http); + + /* Upgrade with encryption... */ + httpEncryption(request->http, HTTP_ENCRYPT_REQUIRED); + + request->attempts++; + goto again; + } +#endif + else if (http_status != HTTP_OK) + { + int http_errno; + + http_errno = httpError (request->http); + + if (http_errno == EPIPE) + request->state = GTK_CUPS_POST_CONNECT; + else + { + request->state = GTK_CUPS_POST_DONE; + gtk_cups_result_set_error (request->result, "HTTP Error in POST %s", strerror (http_errno)); + request->poll_state = GTK_CUPS_HTTP_IDLE; + + httpFlush(request->http); + return; + } + + request->poll_state = GTK_CUPS_HTTP_IDLE; + + httpFlush(request->http); + + request->last_status = HTTP_CONTINUE; + httpClose (request->http); + request->http = NULL; + return; + } + else + { + request->state = GTK_CUPS_POST_READ_RESPONSE; + return; + } + + again: + http_status = HTTP_CONTINUE; + + if (httpCheck (request->http)) + http_status = httpUpdate (request->http); + + request->last_status = http_status; +} + +static void +_post_read_response (GtkCupsRequest *request) +{ + ipp_state_t ipp_status; + + request->poll_state = GTK_CUPS_HTTP_READ; + + if (request->result->ipp_response == NULL) + request->result->ipp_response = ippNew(); + + ipp_status = ippRead (request->http, + request->result->ipp_response); + + if (ipp_status == IPP_ERROR) + { + gtk_cups_result_set_error (request->result, "%s", ippErrorString (cupsLastError())); + + ippDelete (request->result->ipp_response); + request->result->ipp_response = NULL; + + request->state = GTK_CUPS_POST_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + } + else if (ipp_status == IPP_DATA) + { + request->state = GTK_CUPS_POST_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + } +} + +static void +_get_send (GtkCupsRequest *request) +{ + request->poll_state = GTK_CUPS_HTTP_WRITE; + + if (request->data_fd == 0) + { + gtk_cups_result_set_error (request->result, "Get requires an open file descriptor"); + request->state = GTK_CUPS_GET_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + + return; + } + + httpClearFields(request->http); + httpSetField(request->http, HTTP_FIELD_AUTHORIZATION, request->http->authstring); + + if (httpGet(request->http, request->resource)) + { + if (httpReconnect(request->http)) + { + request->state = GTK_CUPS_GET_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + + gtk_cups_result_set_error (request->result, "Failed Get"); + } + + request->attempts++; + return; + } + + request->attempts = 0; + + request->state = GTK_CUPS_GET_CHECK; + request->poll_state = GTK_CUPS_HTTP_READ; + + request->ipp_request->state = IPP_IDLE; +} + +static void +_get_check (GtkCupsRequest *request) +{ + http_status_t http_status; + + http_status = request->last_status; + + request->poll_state = GTK_CUPS_HTTP_READ; + + if (http_status == HTTP_CONTINUE) + { + goto again; + } + else if (http_status == HTTP_UNAUTHORIZED) + { + /* TODO: callout for auth */ + g_warning ("NOT IMPLEMENTED: We need to prompt for authorization in a non blocking manner"); + request->state = GTK_CUPS_GET_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + + gtk_cups_result_set_error (request->result, "Can't prompt for authorization"); + return; + } +/* TODO: detect ssl in configure.ac */ +#if HAVE_SSL + else if (http_status == HTTP_UPGRADE_REQUIRED) + { + /* Flush any error message... */ + httpFlush (request->http); + + /* Reconnect... */ + httpReconnect (request->http); + + /* Upgrade with encryption... */ + httpEncryption(request->http, HTTP_ENCRYPT_REQUIRED); + + request->attempts++; + goto again; + } +#endif + else if (http_status != HTTP_OK) + { + int http_errno; + + http_errno = httpError (request->http); + + if (http_errno == EPIPE) + request->state = GTK_CUPS_GET_CONNECT; + else + { + request->state = GTK_CUPS_GET_DONE; + gtk_cups_result_set_error (request->result, "HTTP Error in GET %s", strerror (http_errno)); + request->poll_state = GTK_CUPS_HTTP_IDLE; + httpFlush(request->http); + + return; + } + + request->poll_state = GTK_CUPS_HTTP_IDLE; + httpFlush (request->http); + httpClose (request->http); + request->last_status = HTTP_CONTINUE; + request->http = NULL; + return; + + } + else + { + request->state = GTK_CUPS_GET_READ_DATA; + return; + } + + again: + http_status = HTTP_CONTINUE; + + if (httpCheck (request->http)) + http_status = httpUpdate (request->http); + + request->last_status = http_status; + +} + +static void +_get_read_data (GtkCupsRequest *request) +{ + char buffer[_GTK_CUPS_MAX_CHUNK_SIZE]; + int bytes; + + request->poll_state = GTK_CUPS_HTTP_READ; + + bytes = httpRead(request->http, buffer, sizeof(buffer)); + + if (bytes == 0) + { + request->state = GTK_CUPS_GET_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + + return; + } + + if (write (request->data_fd, buffer, bytes) == -1) + { + char *error_msg; + + request->state = GTK_CUPS_POST_DONE; + request->poll_state = GTK_CUPS_HTTP_IDLE; + + error_msg = strerror (errno); + gtk_cups_result_set_error (request->result, error_msg ? error_msg:""); + } +} + +gboolean +gtk_cups_request_is_done (GtkCupsRequest *request) +{ + return (request->state == GTK_CUPS_REQUEST_DONE); +} + +gboolean +gtk_cups_result_is_error (GtkCupsResult *result) +{ + return result->is_error; +} + +ipp_t * +gtk_cups_result_get_response (GtkCupsResult *result) +{ + return result->ipp_response; +} + +const char * +gtk_cups_result_get_error_string (GtkCupsResult *result) +{ + return result->error_msg; +} + diff --git a/modules/printbackends/cups/gtkcupsutils.h b/modules/printbackends/cups/gtkcupsutils.h new file mode 100644 index 0000000000..f49cd2e9bc --- /dev/null +++ b/modules/printbackends/cups/gtkcupsutils.h @@ -0,0 +1,125 @@ +/* gtkcupsutils.h + * Copyright (C) 2006 John (J5) Palmieri <johnp@redhat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser 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. + */ + +#ifndef __GTK_CUPS_UTILS_H__ +#define __GTK_CUPS_UTILS_H__ + +#include <glib.h> +#include <cups/cups.h> +#include <cups/language.h> +#include <cups/http.h> +#include <cups/ipp.h> + +G_BEGIN_DECLS + +typedef struct _GtkCupsRequest GtkCupsRequest; +typedef struct _GtkCupsResult GtkCupsResult; + +typedef enum +{ + GTK_CUPS_POST, + GTK_CUPS_GET +} GtkCupsRequestType; + + +/** + * Direction we should be polling the http socket on. + * We are either reading or writting at each state. + * This makes it easy for mainloops to connect to poll. + */ +typedef enum +{ + GTK_CUPS_HTTP_IDLE, + GTK_CUPS_HTTP_READ, + GTK_CUPS_HTTP_WRITE +} GtkCupsPollState; + + +struct _GtkCupsRequest +{ + GtkCupsRequestType type; + + http_t *http; + http_status_t last_status; + ipp_t *ipp_request; + + gchar *server; + gchar *resource; + gint data_fd; + gint attempts; + + GtkCupsResult *result; + + gint state; + GtkCupsPollState poll_state; + + gint own_http : 1; +}; + +#define GTK_CUPS_REQUEST_START 0 +#define GTK_CUPS_REQUEST_DONE 500 + +/* POST states */ +enum +{ + GTK_CUPS_POST_CONNECT = GTK_CUPS_REQUEST_START, + GTK_CUPS_POST_SEND, + GTK_CUPS_POST_WRITE_REQUEST, + GTK_CUPS_POST_WRITE_DATA, + GTK_CUPS_POST_CHECK, + GTK_CUPS_POST_READ_RESPONSE, + GTK_CUPS_POST_DONE = GTK_CUPS_REQUEST_DONE +}; + +/* GET states */ +enum +{ + GTK_CUPS_GET_CONNECT = GTK_CUPS_REQUEST_START, + GTK_CUPS_GET_SEND, + GTK_CUPS_GET_CHECK, + GTK_CUPS_GET_READ_DATA, + GTK_CUPS_GET_DONE = GTK_CUPS_REQUEST_DONE +}; + +GtkCupsRequest * gtk_cups_request_new (http_t *connection, + GtkCupsRequestType req_type, + gint operation_id, + gint data_fd, + const char *server, + const char *resource); +void gtk_cups_request_ipp_add_string (GtkCupsRequest *request, + ipp_tag_t group, + ipp_tag_t tag, + const char *name, + const char *charset, + const char *value); +gboolean gtk_cups_request_read_write (GtkCupsRequest *request); +GtkCupsPollState gtk_cups_request_get_poll_state (GtkCupsRequest *request); +void gtk_cups_request_free (GtkCupsRequest *request); +GtkCupsResult * gtk_cups_request_get_result (GtkCupsRequest *request); +gboolean gtk_cups_request_is_done (GtkCupsRequest *request); +void gtk_cups_request_encode_option (GtkCupsRequest *request, + const gchar *option, + const gchar *value); +gboolean gtk_cups_result_is_error (GtkCupsResult *result); +ipp_t * gtk_cups_result_get_response (GtkCupsResult *result); +const char * gtk_cups_result_get_error_string (GtkCupsResult *result); + +G_END_DECLS +#endif diff --git a/modules/printbackends/cups/gtkprintbackendcups.c b/modules/printbackends/cups/gtkprintbackendcups.c new file mode 100644 index 0000000000..79922a5670 --- /dev/null +++ b/modules/printbackends/cups/gtkprintbackendcups.c @@ -0,0 +1,2629 @@ +/* GTK - The GIMP Toolkit + * gtkprintbackendcups.h: Default implementation of GtkPrintBackend + * for the Common Unix Print System (CUPS) + * Copyright (C) 2003, Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser 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. + */ + +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> + +#include <config.h> +#include <cups/cups.h> +#include <cups/language.h> +#include <cups/http.h> +#include <cups/ipp.h> +#include <errno.h> +#include <cairo.h> +#include <cairo-pdf.h> +#include <cairo-ps.h> + +#include <glib/gi18n-lib.h> +#include <gmodule.h> + +#include <gtk/gtkprintoperation.h> +#include <gtk/gtkprintsettings.h> +#include <gtk/gtkprintbackend.h> +#include <gtk/gtkprinter.h> + +#include "gtkprintbackendcups.h" +#include "gtkprintercups.h" + +#include "gtkcupsutils.h" + + +typedef struct _GtkPrintBackendCupsClass GtkPrintBackendCupsClass; + +#define GTK_PRINT_BACKEND_CUPS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_PRINT_BACKEND_CUPS, GtkPrintBackendCupsClass)) +#define GTK_IS_PRINT_BACKEND_CUPS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_PRINT_BACKEND_CUPS)) +#define GTK_PRINT_BACKEND_CUPS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_PRINT_BACKEND_CUPS, GtkPrintBackendCupsClass)) + +#define _CUPS_MAX_ATTEMPTS 10 +#define _CUPS_MAX_CHUNK_SIZE 8192 + +#define _CUPS_MAP_ATTR_INT(attr, v, a) {if (!g_ascii_strcasecmp (attr->name, (a))) v = attr->values[0].integer;} +#define _CUPS_MAP_ATTR_STR(attr, v, a) {if (!g_ascii_strcasecmp (attr->name, (a))) v = g_strdup (attr->values[0].string.text);} + +static GType print_backend_cups_type = 0; + +typedef void (* GtkPrintCupsResponseCallbackFunc) (GtkPrintBackend *print_backend, + GtkCupsResult *result, + gpointer user_data); + +typedef enum +{ + DISPATCH_SETUP, + DISPATCH_REQUEST, + DISPATCH_SEND, + DISPATCH_CHECK, + DISPATCH_READ, + DISPATCH_ERROR +} GtkPrintCupsDispatchState; + +typedef struct +{ + GSource source; + + http_t *http; + GtkCupsRequest *request; + GPollFD *data_poll; + GtkPrintBackendCups *backend; + +} GtkPrintCupsDispatchWatch; + +struct _GtkPrintBackendCupsClass +{ + GObjectClass parent_class; +}; + +struct _GtkPrintBackendCups +{ + GObject parent_instance; + + GHashTable *printers; + + char *default_printer; + + guint list_printers_poll; + guint list_printers_pending : 1; + guint got_default_printer : 1; +}; + +static GObjectClass *backend_parent_class; + +static void gtk_print_backend_cups_class_init (GtkPrintBackendCupsClass *class); +static void gtk_print_backend_cups_iface_init (GtkPrintBackendIface *iface); +static void gtk_print_backend_cups_init (GtkPrintBackendCups *impl); +static void gtk_print_backend_cups_finalize (GObject *object); +static GList * cups_get_printer_list (GtkPrintBackend *print_backend); +static void cups_request_execute (GtkPrintBackendCups *print_backend, + GtkCupsRequest *request, + GtkPrintCupsResponseCallbackFunc callback, + gpointer user_data, + GDestroyNotify notify, + GError **err); +static void cups_printer_get_settings_from_options (GtkPrinter *printer, + GtkPrinterOptionSet *options, + GtkPrintSettings *settings); +static gboolean cups_printer_mark_conflicts (GtkPrinter *printer, + GtkPrinterOptionSet *options); +static GtkPrinterOptionSet *cups_printer_get_options (GtkPrinter *printer, + GtkPrintSettings *settings, + GtkPageSetup *page_setup); +static void cups_printer_prepare_for_print (GtkPrinter *printer, + GtkPrintJob *print_job, + GtkPrintSettings *settings, + GtkPageSetup *page_setup); +static GList * cups_printer_list_papers (GtkPrinter *printer); +static void cups_printer_request_details (GtkPrinter *printer); +static void cups_request_default_printer (GtkPrintBackendCups *print_backend); +static void cups_request_ppd (GtkPrinter *printer); +static void cups_printer_get_hard_margins (GtkPrinter *printer, + double *top, + double *bottom, + double *left, + double *right); +static void set_option_from_settings (GtkPrinterOption *option, + GtkPrintSettings *setting); +static void cups_begin_polling_info (GtkPrintBackendCups *print_backend, + GtkPrintJob *job, + int job_id); +static gboolean cups_job_info_poll_timeout (gpointer user_data); + +static void +gtk_print_backend_cups_register_type (GTypeModule *module) +{ + if (!print_backend_cups_type) + { + static const GTypeInfo print_backend_cups_info = + { + sizeof (GtkPrintBackendCupsClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gtk_print_backend_cups_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GtkPrintBackendCups), + 0, /* n_preallocs */ + (GInstanceInitFunc) gtk_print_backend_cups_init + }; + + static const GInterfaceInfo print_backend_info = + { + (GInterfaceInitFunc) gtk_print_backend_cups_iface_init, /* interface_init */ + NULL, /* interface_finalize */ + NULL /* interface_data */ + }; + + print_backend_cups_type = g_type_module_register_type (module, + G_TYPE_OBJECT, + "GtkPrintBackendCups", + &print_backend_cups_info, 0); + g_type_module_add_interface (module, + print_backend_cups_type, + GTK_TYPE_PRINT_BACKEND, + &print_backend_info); + } +} + +G_MODULE_EXPORT void +pb_module_init (GTypeModule *module) +{ + gtk_print_backend_cups_register_type (module); + gtk_printer_cups_register_type (module); +} + +G_MODULE_EXPORT void +pb_module_exit (void) +{ + +} + +G_MODULE_EXPORT GtkPrintBackend * +pb_module_create (void) +{ + return gtk_print_backend_cups_new (); +} + +/* + * GtkPrintBackendCups + */ +GType +gtk_print_backend_cups_get_type (void) +{ + return print_backend_cups_type; +} + +/** + * gtk_print_backend_cups_new: + * + * Creates a new #GtkPrintBackendCups object. #GtkPrintBackendCups + * implements the #GtkPrintBackend interface with direct access to + * the filesystem using Unix/Linux API calls + * + * Return value: the new #GtkPrintBackendCups object + **/ +GtkPrintBackend * +gtk_print_backend_cups_new (void) +{ + return g_object_new (GTK_TYPE_PRINT_BACKEND_CUPS, NULL); +} + +static void +gtk_print_backend_cups_class_init (GtkPrintBackendCupsClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + backend_parent_class = g_type_class_peek_parent (class); + + gobject_class->finalize = gtk_print_backend_cups_finalize; +} + +static cairo_status_t +_cairo_write_to_cups (void *cache_fd_as_pointer, + const unsigned char *data, + unsigned int length) +{ + cairo_status_t result; + gint cache_fd; + cache_fd = GPOINTER_TO_INT (cache_fd_as_pointer); + + result = CAIRO_STATUS_WRITE_ERROR; + + /* write out the buffer */ + if (write (cache_fd, data, length) != -1) + result = CAIRO_STATUS_SUCCESS; + + return result; +} + + +static cairo_surface_t * +cups_printer_create_cairo_surface (GtkPrinter *printer, + gdouble width, + gdouble height, + gint cache_fd) +{ + cairo_surface_t *surface; + + /* TODO: check if it is a ps or pdf printer */ + + surface = cairo_ps_surface_create_for_stream (_cairo_write_to_cups, GINT_TO_POINTER (cache_fd), width, height); + + /* TODO: DPI from settings object? */ + cairo_ps_surface_set_dpi (surface, 300, 300); + + return surface; +} + +static GtkPrinter * +gtk_print_backend_cups_find_printer (GtkPrintBackend *print_backend, + const gchar *printer_name) +{ + GtkPrintBackendCups *cups_print_backend; + + cups_print_backend = GTK_PRINT_BACKEND_CUPS (print_backend); + + return (GtkPrinter *) g_hash_table_lookup (cups_print_backend->printers, + printer_name); +} + +typedef struct { + GtkPrintJobCompleteFunc callback; + GtkPrintJob *job; + gpointer user_data; + GDestroyNotify dnotify; +} CupsPrintStreamData; + +static void +cups_free_print_stream_data (CupsPrintStreamData *data) +{ + if (data->dnotify) + data->dnotify (data->user_data); + g_object_unref (data->job); + g_free (data); +} + +static void +cups_print_cb (GtkPrintBackendCups *print_backend, + GtkCupsResult *result, + gpointer user_data) +{ + GError *error = NULL; + CupsPrintStreamData *ps = user_data; + + if (gtk_cups_result_is_error (result)) + error = g_error_new_literal (gtk_print_error_quark (), + GTK_PRINT_ERROR_INTERNAL_ERROR, + gtk_cups_result_get_error_string (result)); + + if (ps->callback) + ps->callback (ps->job, ps->user_data, error); + + if (error == NULL) + { + int job_id = 0; + ipp_attribute_t *attr; /* IPP job-id attribute */ + ipp_t *response = gtk_cups_result_get_response (result); + + if ((attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) != NULL) + job_id = attr->values[0].integer; + + + if (job_id == 0) + gtk_print_job_set_status (ps->job, GTK_PRINT_STATUS_FINISHED); + else + { + gtk_print_job_set_status (ps->job, GTK_PRINT_STATUS_PENDING); + cups_begin_polling_info (print_backend, ps->job, job_id); + } + } + else + gtk_print_job_set_status (ps->job, GTK_PRINT_STATUS_FINISHED_ABORTED); + + + if (error) + g_error_free (error); + +} + +static void +add_cups_options (const char *key, + const char *value, + gpointer user_data) +{ + GtkCupsRequest *request = user_data; + + if (!g_str_has_prefix (key, "cups-")) + return; + + if (strcmp (value, "gtk-ignore-value") == 0) + return; + + key = key + strlen("cups-"); + + gtk_cups_request_encode_option (request, key, value); +} + +static void +gtk_print_backend_cups_print_stream (GtkPrintBackend *print_backend, + GtkPrintJob *job, + gint data_fd, + GtkPrintJobCompleteFunc callback, + gpointer user_data, + GDestroyNotify dnotify) +{ + GError *error; + GtkPrinterCups *cups_printer; + CupsPrintStreamData *ps; + GtkCupsRequest *request; + GtkPrintSettings *settings; + const gchar *title; + + cups_printer = GTK_PRINTER_CUPS (gtk_print_job_get_printer (job)); + settings = gtk_print_job_get_settings (job); + + error = NULL; + + request = gtk_cups_request_new (NULL, + GTK_CUPS_POST, + IPP_PRINT_JOB, + data_fd, + NULL, + cups_printer->device_uri); + + gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", + NULL, cups_printer->printer_uri); + + gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", + NULL, cupsUser()); + + title = gtk_print_job_get_title (job); + if (title) + gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL, + title); + + gtk_print_settings_foreach (settings, add_cups_options, request); + + ps = g_new0 (CupsPrintStreamData, 1); + ps->callback = callback; + ps->user_data = user_data; + ps->dnotify = dnotify; + ps->job = g_object_ref (job); + + cups_request_execute (GTK_PRINT_BACKEND_CUPS (print_backend), + request, + (GtkPrintCupsResponseCallbackFunc) cups_print_cb, + ps, + (GDestroyNotify)cups_free_print_stream_data, + &error); +} + + +static void +gtk_print_backend_cups_iface_init (GtkPrintBackendIface *iface) +{ + iface->get_printer_list = cups_get_printer_list; + iface->find_printer = gtk_print_backend_cups_find_printer; + iface->print_stream = gtk_print_backend_cups_print_stream; + iface->printer_request_details = cups_printer_request_details; + iface->printer_create_cairo_surface = cups_printer_create_cairo_surface; + iface->printer_get_options = cups_printer_get_options; + iface->printer_mark_conflicts = cups_printer_mark_conflicts; + iface->printer_get_settings_from_options = cups_printer_get_settings_from_options; + iface->printer_prepare_for_print = cups_printer_prepare_for_print; + iface->printer_list_papers = cups_printer_list_papers; + iface->printer_get_hard_margins = cups_printer_get_hard_margins; +} + +static void +gtk_print_backend_cups_init (GtkPrintBackendCups *backend_cups) +{ + backend_cups->list_printers_poll = 0; + backend_cups->list_printers_pending = FALSE; + backend_cups->printers = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_object_unref); + + cups_request_default_printer (backend_cups); +} + +static void +gtk_print_backend_cups_finalize (GObject *object) +{ + GtkPrintBackendCups *backend_cups; + + backend_cups = GTK_PRINT_BACKEND_CUPS (object); + + if (backend_cups->list_printers_poll > 0) + g_source_remove (backend_cups->list_printers_poll); + + if (backend_cups->printers) + g_hash_table_unref (backend_cups->printers); + + g_free (backend_cups->default_printer); + backend_cups->default_printer = NULL; + + backend_parent_class->finalize (object); +} + +static gboolean +cups_dispatch_watch_check (GSource *source) +{ + GtkPrintCupsDispatchWatch *dispatch; + GtkCupsPollState poll_state; + gboolean result; + + dispatch = (GtkPrintCupsDispatchWatch *) source; + + poll_state = gtk_cups_request_get_poll_state (dispatch->request); + + if (dispatch->data_poll == NULL && + dispatch->request->http != NULL) + { + dispatch->data_poll = g_new0 (GPollFD, 1); + dispatch->data_poll->fd = dispatch->request->http->fd; + + g_source_add_poll (source, dispatch->data_poll); + } + + if (dispatch->data_poll != NULL && dispatch->request->http != NULL) + { + if (dispatch->data_poll->fd != dispatch->request->http->fd) + dispatch->data_poll->fd = dispatch->request->http->fd; + + if (poll_state == GTK_CUPS_HTTP_READ) + dispatch->data_poll->events = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_PRI; + else if (poll_state == GTK_CUPS_HTTP_WRITE) + dispatch->data_poll->events = G_IO_OUT | G_IO_ERR; + else + dispatch->data_poll->events = 0; + } + + if (poll_state != GTK_CUPS_HTTP_IDLE) + if (!(dispatch->data_poll->revents & dispatch->data_poll->events)) + return FALSE; + + result = gtk_cups_request_read_write (dispatch->request); + if (result && dispatch->data_poll != NULL) + { + g_source_remove_poll (source, dispatch->data_poll); + g_free (dispatch->data_poll); + dispatch->data_poll = NULL; + } + + return result; +} + +static gboolean +cups_dispatch_watch_prepare (GSource *source, + gint *timeout_) +{ + GtkPrintCupsDispatchWatch *dispatch; + + dispatch = (GtkPrintCupsDispatchWatch *) source; + + + *timeout_ = -1; + + return gtk_cups_request_read_write (dispatch->request); +} + +static gboolean +cups_dispatch_watch_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) +{ + GtkPrintCupsDispatchWatch *dispatch; + GtkPrintCupsResponseCallbackFunc ep_callback; + GtkCupsResult *result; + + g_assert (callback != NULL); + + ep_callback = (GtkPrintCupsResponseCallbackFunc) callback; + + dispatch = (GtkPrintCupsDispatchWatch *) source; + + result = gtk_cups_request_get_result (dispatch->request); + + if (gtk_cups_result_is_error (result)) + g_warning (gtk_cups_result_get_error_string (result)); + + ep_callback (GTK_PRINT_BACKEND (dispatch->backend), result, user_data); + + g_source_unref (source); + return FALSE; +} + +static void +cups_dispatch_watch_finalize (GSource *source) +{ + GtkPrintCupsDispatchWatch *dispatch; + + dispatch = (GtkPrintCupsDispatchWatch *) source; + + gtk_cups_request_free (dispatch->request); + + if (dispatch->backend) + { + g_object_unref (dispatch->backend); + dispatch->backend = NULL; + } + + if (dispatch->data_poll != NULL) + g_free (dispatch->data_poll); +} + +static GSourceFuncs _cups_dispatch_watch_funcs = { + cups_dispatch_watch_prepare, + cups_dispatch_watch_check, + cups_dispatch_watch_dispatch, + cups_dispatch_watch_finalize +}; + + +static void +cups_request_execute (GtkPrintBackendCups *print_backend, + GtkCupsRequest *request, + GtkPrintCupsResponseCallbackFunc callback, + gpointer user_data, + GDestroyNotify notify, + GError **err) +{ + GtkPrintCupsDispatchWatch *dispatch; + + dispatch = (GtkPrintCupsDispatchWatch *) g_source_new (&_cups_dispatch_watch_funcs, + sizeof (GtkPrintCupsDispatchWatch)); + + dispatch->request = request; + dispatch->backend = g_object_ref (print_backend); + dispatch->data_poll = NULL; + + g_source_set_callback ((GSource *) dispatch, (GSourceFunc) callback, user_data, notify); + + g_source_attach ((GSource *) dispatch, NULL); +} + +static void +cups_request_printer_info_cb (GtkPrintBackendCups *print_backend, + GtkCupsResult *result, + gpointer user_data) +{ + ipp_attribute_t *attr; + ipp_t *response; + gchar *printer_name; + GtkPrinterCups *cups_printer; + GtkPrinter *printer; + gchar *printer_uri; + gchar *member_printer_uri; + gchar *loc; + gchar *desc; + gchar *state_msg; + int job_count; + + char uri[HTTP_MAX_URI], /* Printer URI */ + method[HTTP_MAX_URI], /* Method/scheme name */ + username[HTTP_MAX_URI], /* Username:password */ + hostname[HTTP_MAX_URI], /* Hostname */ + resource[HTTP_MAX_URI]; /* Resource name */ + int port; /* Port number */ + gboolean status_changed; + + g_assert (GTK_IS_PRINT_BACKEND_CUPS (print_backend)); + + printer_uri = NULL; + member_printer_uri = NULL; + + printer_name = (gchar *)user_data; + cups_printer = (GtkPrinterCups *) g_hash_table_lookup (print_backend->printers, printer_name); + + if (!cups_printer) + return; + + printer = GTK_PRINTER (cups_printer); + + if (gtk_cups_result_is_error (result)) + { + if (gtk_printer_is_new (printer)) + { + g_hash_table_remove (print_backend->printers, + printer_name); + return; + } + else + return; /* TODO: mark as inactive printer */ + } + + response = gtk_cups_result_get_response (result); + + /* TODO: determine printer type and use correct icon */ + gtk_printer_set_icon_name (printer, "printer"); + + cups_printer->device_uri = g_strdup_printf ("/printers/%s", printer_name); + + state_msg = ""; + loc = ""; + desc = ""; + job_count = 0; + for (attr = response->attrs; attr != NULL; attr = attr->next) + { + if (!attr->name) + continue; + + _CUPS_MAP_ATTR_STR (attr, loc, "printer-location"); + _CUPS_MAP_ATTR_STR (attr, desc, "printer-info"); + _CUPS_MAP_ATTR_STR (attr, state_msg, "printer-state-message"); + _CUPS_MAP_ATTR_STR (attr, printer_uri, "printer-uri-supported"); + _CUPS_MAP_ATTR_STR (attr, member_printer_uri, "member-uris"); + _CUPS_MAP_ATTR_INT (attr, cups_printer->state, "printer-state"); + _CUPS_MAP_ATTR_INT (attr, job_count, "queued-job-count"); + } + + /* if we got a member_printer_uri then this printer is part of a class + so use member_printer_uri, else user printer_uri */ + + if (cups_printer->printer_uri) + g_free (cups_printer->printer_uri); + + if (member_printer_uri) + { + g_free (printer_uri); + cups_printer->printer_uri = member_printer_uri; + } + else + cups_printer->printer_uri = printer_uri; + + status_changed = gtk_printer_set_job_count (printer, job_count); + + status_changed |= gtk_printer_set_location (printer, loc); + status_changed |= gtk_printer_set_description (printer, desc); + status_changed |= gtk_printer_set_state_message (printer, state_msg); + +#if (CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR >= 2) || CUPS_VERSION_MAJOR > 1 + httpSeparateURI (HTTP_URI_CODING_ALL, cups_printer->printer_uri, + method, sizeof (method), + username, sizeof (username), + hostname, sizeof (hostname), + &port, + resource, sizeof (resource)); + +#else + httpSeparate (cups_printer->printer_uri, + method, + username, + hostname, + &port, + resource); +#endif + + gethostname(uri, sizeof(uri)); + + if (strcasecmp(uri, hostname) == 0) + strcpy(hostname, "localhost"); + + if (cups_printer->hostname) + g_free (cups_printer->hostname); + + cups_printer->hostname = g_strdup (hostname); + cups_printer->port = port; + + if (status_changed) + g_signal_emit_by_name (GTK_PRINT_BACKEND (print_backend), "printer-status-changed", printer); +} + +static void +cups_request_printer_info (GtkPrintBackendCups *print_backend, + const gchar *printer_name) +{ + GError *error; + GtkCupsRequest *request; + gchar *printer_uri; + + error = NULL; + + request = gtk_cups_request_new (NULL, + GTK_CUPS_POST, + IPP_GET_PRINTER_ATTRIBUTES, + 0, + NULL, + NULL); + + printer_uri = g_strdup_printf ("ipp://localhost/printers/%s", + printer_name); + gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_URI, + "printer-uri", NULL, printer_uri); + + g_free (printer_uri); + + cups_request_execute (print_backend, + request, + (GtkPrintCupsResponseCallbackFunc) cups_request_printer_info_cb, + g_strdup (printer_name), + (GDestroyNotify) g_free, + &error); + +} + + +typedef struct { + GtkPrintBackendCups *print_backend; + GtkPrintJob *job; + int job_id; + int counter; +} CupsJobPollData; + +static void +job_object_died (gpointer user_data, + GObject *where_the_object_was) +{ + CupsJobPollData *data = user_data; + data->job = NULL; +} + +static void +cups_job_poll_data_free (CupsJobPollData *data) +{ + if (data->job) + g_object_weak_unref (G_OBJECT (data->job), job_object_died, data); + + g_free (data); +} + +static void +cups_request_job_info_cb (GtkPrintBackendCups *print_backend, + GtkCupsResult *result, + gpointer user_data) +{ + CupsJobPollData *data = user_data; + ipp_attribute_t *attr; + ipp_t *response; + int state; + gboolean done; + + if (data->job == NULL) + { + cups_job_poll_data_free (data); + return; + } + + data->counter++; + + response = gtk_cups_result_get_response (result); + + state = 0; + for (attr = response->attrs; attr != NULL; attr = attr->next) + { + if (!attr->name) + continue; + + _CUPS_MAP_ATTR_INT (attr, state, "job-state"); + } + + done = FALSE; + switch (state) + { + case IPP_JOB_PENDING: + case IPP_JOB_HELD: + case IPP_JOB_STOPPED: + gtk_print_job_set_status (data->job, + GTK_PRINT_STATUS_PENDING); + break; + case IPP_JOB_PROCESSING: + gtk_print_job_set_status (data->job, + GTK_PRINT_STATUS_PRINTING); + break; + default: + case IPP_JOB_CANCELLED: + case IPP_JOB_ABORTED: + gtk_print_job_set_status (data->job, + GTK_PRINT_STATUS_FINISHED_ABORTED); + done = TRUE; + break; + case 0: + case IPP_JOB_COMPLETED: + gtk_print_job_set_status (data->job, + GTK_PRINT_STATUS_FINISHED); + done = TRUE; + break; + } + + if (!done && data->job != NULL) + { + guint32 timeout; + + if (data->counter < 5) + timeout = 100; + else if (data->counter < 10) + timeout = 500; + else + timeout = 1000; + + g_timeout_add (timeout, cups_job_info_poll_timeout, data); + } + else + cups_job_poll_data_free (data); +} + +static void +cups_request_job_info (CupsJobPollData *data) +{ + GError *error; + GtkCupsRequest *request; + gchar *printer_uri; + + + error = NULL; + request = gtk_cups_request_new (NULL, + GTK_CUPS_POST, + IPP_GET_JOB_ATTRIBUTES, + 0, + NULL, + NULL); + + printer_uri = g_strdup_printf ("ipp://localhost/jobs/%d", data->job_id); + gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION, IPP_TAG_URI, + "job-uri", NULL, printer_uri); + g_free (printer_uri); + + cups_request_execute (data->print_backend, + request, + (GtkPrintCupsResponseCallbackFunc) cups_request_job_info_cb, + data, + NULL, + &error); +} + +static gboolean +cups_job_info_poll_timeout (gpointer user_data) +{ + CupsJobPollData *data = user_data; + + if (data->job == NULL) + cups_job_poll_data_free (data); + else + cups_request_job_info (data); + + return FALSE; +} + +static void +cups_begin_polling_info (GtkPrintBackendCups *print_backend, + GtkPrintJob *job, + int job_id) +{ + CupsJobPollData *data; + + data = g_new0 (CupsJobPollData, 1); + + data->print_backend = print_backend; + data->job = job; + data->job_id = job_id; + data->counter = 0; + + g_object_weak_ref (G_OBJECT (job), job_object_died, data); + + cups_request_job_info (data); +} + +static gint +printer_cmp (GtkPrinter *a, GtkPrinter *b) +{ + const char *name_a, *name_b; + g_assert (GTK_IS_PRINTER (a) && GTK_IS_PRINTER (b)); + + name_a = gtk_printer_get_name (a); + name_b = gtk_printer_get_name (b); + if (name_a == NULL && name_b == NULL) + return 0; + else if (name_a == NULL) + return G_MAXINT; + else if (name_b == NULL) + return G_MININT; + else + return g_ascii_strcasecmp (name_a, name_b); +} + +static void +printer_hash_to_sorted_active_list (const gchar *key, + gpointer value, + GList **out_list) +{ + GtkPrinter *printer; + + printer = GTK_PRINTER (value); + + if (gtk_printer_get_name (printer) == NULL) + return; + + if (!gtk_printer_is_active (printer)) + return; + + *out_list = g_list_insert_sorted (*out_list, value, (GCompareFunc) printer_cmp); +} + +static void +printer_hash_to_sorted_active_name_list (const gchar *key, + gpointer value, + GList **out_list) +{ + GtkPrinter *printer; + + printer = GTK_PRINTER (value); + + + if (gtk_printer_get_name (printer) == NULL) + return; + + if (!gtk_printer_is_active (printer)) + return; + + if (gtk_printer_is_active (printer)) + *out_list = g_list_insert_sorted (*out_list, + (char *)gtk_printer_get_name (printer), + g_str_equal); +} + +static void +mark_printer_inactive (const gchar *printer_name, + GtkPrintBackendCups *cups_backend) +{ + GtkPrinter *printer; + GHashTable *printer_hash; + + printer_hash = cups_backend->printers; + + printer = (GtkPrinter *) g_hash_table_lookup (printer_hash, + printer_name); + + if (printer == NULL) + return; + + gtk_printer_set_is_active (printer, FALSE); + + g_signal_emit_by_name (GTK_PRINT_BACKEND (cups_backend), "printer-removed", printer); +} + +static void +cups_request_printer_list_cb (GtkPrintBackendCups *cups_backend, + GtkCupsResult *result, + gpointer user_data) +{ + ipp_attribute_t *attr; + ipp_t *response; + gboolean list_has_changed; + GList *removed_printer_checklist; + + list_has_changed = FALSE; + + g_assert (GTK_IS_PRINT_BACKEND_CUPS (cups_backend)); + + cups_backend->list_printers_pending = FALSE; + + if (gtk_cups_result_is_error (result)) + { + g_warning ("Error getting printer list: %s", gtk_cups_result_get_error_string (result)); + return; + } + + /* gether the names of the printers in the current queue + so we may check to see if they were removed */ + removed_printer_checklist = NULL; + if (cups_backend->printers != NULL) + g_hash_table_foreach (cups_backend->printers, + (GHFunc) printer_hash_to_sorted_active_name_list, + &removed_printer_checklist); + + response = gtk_cups_result_get_response (result); + + attr = ippFindAttribute (response, "printer-name", IPP_TAG_NAME); + + while (attr) + { + GtkPrinterCups *cups_printer; + GtkPrinter *printer; + const gchar *printer_name; + GList *node; + + printer_name = attr->values[0].string.text; + /* remove name from checklist if it was found */ + node = g_list_find_custom (removed_printer_checklist, printer_name, (GCompareFunc) g_ascii_strcasecmp); + removed_printer_checklist = g_list_delete_link (removed_printer_checklist, node); + + cups_printer = (GtkPrinterCups *) g_hash_table_lookup (cups_backend->printers, + printer_name); + printer = cups_printer ? GTK_PRINTER (cups_printer) : NULL; + + if (!cups_printer) + { + list_has_changed = TRUE; + cups_printer = gtk_printer_cups_new (attr->values[0].string.text, + GTK_PRINT_BACKEND (cups_backend)); + printer = GTK_PRINTER (cups_printer); + + if (cups_backend->default_printer != NULL && + strcmp (cups_backend->default_printer, gtk_printer_get_name (printer)) == 0) + gtk_printer_set_is_default (printer, TRUE); + + g_hash_table_insert (cups_backend->printers, + g_strdup (gtk_printer_get_name (printer)), + cups_printer); + } + + if (!gtk_printer_is_active (printer)) + { + gtk_printer_set_is_active (printer, TRUE); + gtk_printer_set_is_new (printer, TRUE); + list_has_changed = TRUE; + } + + if (gtk_printer_is_new (printer)) + { + g_signal_emit_by_name (GTK_PRINT_BACKEND (cups_backend), + "printer-added", + printer); + + gtk_printer_set_is_new (printer, FALSE); + } + + cups_request_printer_info (cups_backend, gtk_printer_get_name (printer)); + + attr = ippFindNextAttribute (response, + "printer-name", + IPP_TAG_NAME); + } + + /* look at the removed printers checklist and mark any printer + as inactive if it is in the list, emitting a printer_removed signal */ + + if (removed_printer_checklist != NULL) + { + g_list_foreach (removed_printer_checklist, (GFunc) mark_printer_inactive, cups_backend); + g_list_free (removed_printer_checklist); + list_has_changed = TRUE; + } + + if (list_has_changed) + g_signal_emit_by_name (GTK_PRINT_BACKEND (cups_backend), "printer-list-changed"); + +} + +static gboolean +cups_request_printer_list (GtkPrintBackendCups *cups_backend) +{ + GError *error; + GtkCupsRequest *request; + + if (cups_backend->list_printers_pending || + !cups_backend->got_default_printer) + return TRUE; + + cups_backend->list_printers_pending = TRUE; + + error = NULL; + + request = gtk_cups_request_new (NULL, + GTK_CUPS_POST, + CUPS_GET_PRINTERS, + 0, + NULL, + NULL); + + cups_request_execute (cups_backend, + request, + (GtkPrintCupsResponseCallbackFunc) cups_request_printer_list_cb, + request, + NULL, + &error); + + + return TRUE; +} + +static GList * +cups_get_printer_list (GtkPrintBackend *print_backend) +{ + GtkPrintBackendCups *cups_backend; + GList *result; + + cups_backend = GTK_PRINT_BACKEND_CUPS (print_backend); + + result = NULL; + if (cups_backend->printers != NULL) + g_hash_table_foreach (cups_backend->printers, + (GHFunc) printer_hash_to_sorted_active_list, + &result); + + if (cups_backend->list_printers_poll == 0) + { + cups_request_printer_list (cups_backend); + cups_backend->list_printers_poll = g_timeout_add (3000, + (GSourceFunc) cups_request_printer_list, + print_backend); + } + + return result; +} + +typedef struct { + GtkPrinterCups *printer; + gint ppd_fd; + gchar *ppd_filename; +} GetPPDData; + +static void +get_ppd_data_free (GetPPDData *data) +{ + close (data->ppd_fd); + unlink (data->ppd_filename); + g_free (data->ppd_filename); + g_object_unref (data->printer); + g_free (data); +} + +static void +cups_request_ppd_cb (GtkPrintBackendCups *print_backend, + GtkCupsResult *result, + GetPPDData *data) +{ + ipp_t *response; + GtkPrinter *printer; + + printer = GTK_PRINTER (data->printer); + GTK_PRINTER_CUPS (printer)->reading_ppd = FALSE; + + if (gtk_cups_result_is_error (result)) + { + g_signal_emit_by_name (printer, "details-acquired", printer, FALSE); + return; + } + + response = gtk_cups_result_get_response (result); + + data->printer->ppd_file = ppdOpenFile (data->ppd_filename); + gtk_printer_set_has_details (printer, TRUE); + g_signal_emit_by_name (printer, "details-acquired", printer, TRUE); +} + +static void +cups_request_ppd (GtkPrinter *printer) +{ + GError *error; + GtkPrintBackend *print_backend; + GtkPrinterCups *cups_printer; + GtkCupsRequest *request; + gchar *resource; + http_t *http; + GetPPDData *data; + + cups_printer = GTK_PRINTER_CUPS (printer); + + error = NULL; + + http = httpConnectEncrypt(cups_printer->hostname, + cups_printer->port, + cupsEncryption()); + + data = g_new0 (GetPPDData, 1); + + data->ppd_fd = g_file_open_tmp ("gtkprint_ppd_XXXXXX", + &data->ppd_filename, + &error); + + if (error != NULL) + { + g_warning ("%s", error->message); + g_error_free (error); + httpClose (http); + g_free (data); + + g_signal_emit_by_name (printer, "details-acquired", printer, FALSE); + return; + } + + fchmod (data->ppd_fd, S_IRUSR | S_IWUSR); + + data->printer = g_object_ref (printer); + + resource = g_strdup_printf ("/printers/%s.ppd", gtk_printer_get_name (printer)); + request = gtk_cups_request_new (http, + GTK_CUPS_GET, + 0, + data->ppd_fd, + cups_printer->hostname, + resource); + + g_free (resource); + + cups_printer->reading_ppd = TRUE; + + print_backend = gtk_printer_get_backend (printer); + + cups_request_execute (GTK_PRINT_BACKEND_CUPS (print_backend), + request, + (GtkPrintCupsResponseCallbackFunc) cups_request_ppd_cb, + data, + (GDestroyNotify)get_ppd_data_free, + &error); +} + + +static void +cups_request_default_printer_cb (GtkPrintBackendCups *print_backend, + GtkCupsResult *result, + gpointer user_data) +{ + ipp_t *response; + ipp_attribute_t *attr; + + response = gtk_cups_result_get_response (result); + + if ((attr = ippFindAttribute(response, "printer-name", IPP_TAG_NAME)) != NULL) + print_backend->default_printer = g_strdup (attr->values[0].string.text); + + print_backend->got_default_printer = TRUE; + + /* Make sure to kick off get_printers if we are polling it, as we could + have blocked this reading the default printer */ + if (print_backend->list_printers_poll != 0) + cups_request_printer_list (print_backend); +} + +static void +cups_request_default_printer (GtkPrintBackendCups *print_backend) +{ + GError *error; + GtkCupsRequest *request; + const char *str; + + error = NULL; + + if ((str = getenv("LPDEST")) != NULL) + { + print_backend->default_printer = g_strdup (str); + print_backend->got_default_printer = TRUE; + return; + } + else if ((str = getenv("PRINTER")) != NULL && + strcmp(str, "lp") != 0) + { + print_backend->default_printer = g_strdup (str); + print_backend->got_default_printer = TRUE; + return; + } + + request = gtk_cups_request_new (NULL, + GTK_CUPS_POST, + CUPS_GET_DEFAULT, + 0, + NULL, + NULL); + + cups_request_execute (print_backend, + request, + (GtkPrintCupsResponseCallbackFunc) cups_request_default_printer_cb, + g_object_ref (print_backend), + g_object_unref, + &error); +} + + +static void +cups_printer_request_details (GtkPrinter *printer) +{ + GtkPrinterCups *cups_printer; + + cups_printer = GTK_PRINTER_CUPS (printer); + if (!cups_printer->reading_ppd && + gtk_printer_cups_get_ppd (cups_printer) == NULL) + cups_request_ppd (printer); +} + +static char * +ppd_text_to_utf8 (ppd_file_t *ppd_file, const char *text) +{ + const char *encoding = NULL; + char *res; + + if (g_ascii_strcasecmp (ppd_file->lang_encoding, "UTF-8") == 0) + { + return g_strdup (text); + } + else if (g_ascii_strcasecmp (ppd_file->lang_encoding, "ISOLatin1") == 0) + { + encoding = "ISO-8859-1"; + } + else if (g_ascii_strcasecmp (ppd_file->lang_encoding, "ISOLatin2") == 0) + { + encoding = "ISO-8859-2"; + } + else if (g_ascii_strcasecmp (ppd_file->lang_encoding, "ISOLatin5") == 0) + { + encoding = "ISO-8859-5"; + } + else if (g_ascii_strcasecmp (ppd_file->lang_encoding, "JIS83-RKSJ") == 0) + { + encoding = "SHIFT-JIS"; + } + else if (g_ascii_strcasecmp (ppd_file->lang_encoding, "MacStandard") == 0) + { + encoding = "MACINTOSH"; + } + else if (g_ascii_strcasecmp (ppd_file->lang_encoding, "WindowsANSI") == 0) + { + encoding = "WINDOWS-1252"; + } + else + { + /* Fallback, try iso-8859-1... */ + encoding = "ISO-8859-1"; + } + + res = g_convert (text, -1, "UTF-8", encoding, NULL, NULL, NULL); + + if (res == NULL) + { + g_warning ("unable to convert PPD text"); + res = g_strdup ("???"); + } + + return res; +} + +/* TODO: Add more translations for common settings here */ + +static const struct { + const char *keyword; + const char *translation; +} cups_option_translations[] = { + { "Duplex", N_("Two Sided") }, +}; + + +static const struct { + const char *keyword; + const char *choice; + const char *translation; +} cups_choice_translations[] = { + { "Duplex", "None", N_("One Sided") }, + { "InputSlot", "Auto", N_("Auto Select") }, + { "InputSlot", "AutoSelect", N_("Auto Select") }, + { "InputSlot", "Default", N_("Printer Default") }, + { "InputSlot", "None", N_("Printer Default") }, + { "InputSlot", "PrinterDefault", N_("Printer Default") }, + { "InputSlot", "Unspecified", N_("Auto Select") }, +}; + +static const struct { + const char *ppd_keyword; + const char *name; +} option_names[] = { + {"Duplex", "gtk-duplex" }, + {"MediaType", "gtk-paper-type"}, + {"InputSlot", "gtk-paper-source"}, + {"OutputBin", "gtk-output-tray"}, +}; + +/* keep sorted when changing */ +static const char *color_option_whitelist[] = { + "BRColorEnhancement", + "BRColorMatching", + "BRColorMatching", + "BRColorMode", + "BRGammaValue", + "BRImprovedGray", + "BlackSubstitution", + "ColorModel", + "HPCMYKInks", + "HPCSGraphics", + "HPCSImages", + "HPCSText", + "HPColorSmart", + "RPSBlackMode", + "RPSBlackOverPrint", + "Rcmyksimulation", +}; + +/* keep sorted when changing */ +static const char *color_group_whitelist[] = { + "ColorPage", + "FPColorWise1", + "FPColorWise2", + "FPColorWise3", + "FPColorWise4", + "FPColorWise5", + "HPColorOptionsPanel", +}; + +/* keep sorted when changing */ +static const char *image_quality_option_whitelist[] = { + "BRDocument", + "BRHalfTonePattern", + "BRNormalPrt", + "BRPrintQuality", + "BitsPerPixel", + "Darkness", + "Dithering", + "EconoMode", + "Economode", + "HPEconoMode", + "HPEdgeControl", + "HPGraphicsHalftone", + "HPHalftone", + "HPLJDensity", + "HPPhotoHalftone", + "OutputMode", + "REt", + "RPSBitsPerPixel", + "RPSDitherType", + "Resolution", + "ScreenLock", + "Smoothing", + "TonerSaveMode", + "UCRGCRForImage", +}; + +/* keep sorted when changing */ +static const char *image_quality_group_whitelist[] = { + "FPImageQuality1", + "FPImageQuality2", + "FPImageQuality3", + "ImageQualityPage", +}; + +/* keep sorted when changing */ +static const char * finishing_option_whitelist[] = { + "BindColor", + "BindEdge", + "BindType", + "BindWhen", + "Booklet", + "FoldType", + "FoldWhen", + "HPStaplerOptions", + "Jog", + "Slipsheet", + "Sorter", + "StapleLocation", + "StapleOrientation", + "StapleWhen", + "StapleX", + "StapleY", +}; + +/* keep sorted when changing */ +static const char *finishing_group_whitelist[] = { + "FPFinishing1", + "FPFinishing2", + "FPFinishing3", + "FPFinishing4", + "FinishingPage", + "HPFinishingPanel", +}; + +/* keep sorted when changing */ +static const char *cups_option_blacklist[] = { + "Collate", + "Copies", + "OutputOrder", + "PageRegion", + "PageSize", +}; + +static char * +get_option_text (ppd_file_t *ppd_file, ppd_option_t *option) +{ + int i; + char *utf8; + + for (i = 0; i < G_N_ELEMENTS (cups_option_translations); i++) + { + if (strcmp (cups_option_translations[i].keyword, option->keyword) == 0) + return g_strdup (_(cups_option_translations[i].translation)); + } + + utf8 = ppd_text_to_utf8 (ppd_file, option->text); + + /* Some ppd files have spaces in the text before the colon */ + g_strchomp (utf8); + + return utf8; +} + +static char * +get_choice_text (ppd_file_t *ppd_file, ppd_choice_t *choice) +{ + int i; + ppd_option_t *option = choice->option; + const char *keyword = option->keyword; + + for (i = 0; i < G_N_ELEMENTS (cups_choice_translations); i++) + { + if (strcmp (cups_choice_translations[i].keyword, keyword) == 0 && + strcmp (cups_choice_translations[i].choice, choice->choice) == 0) + return g_strdup (_(cups_choice_translations[i].translation)); + } + return ppd_text_to_utf8 (ppd_file, choice->text); +} + +static gboolean +group_has_option (ppd_group_t *group, ppd_option_t *option) +{ + int i; + + if (group == NULL) + return FALSE; + + if (group->num_options > 0 && + option >= group->options && option < group->options + group->num_options) + return TRUE; + + for (i = 0; i < group->num_subgroups; i++) + { + if (group_has_option (&group->subgroups[i],option)) + return TRUE; + } + return FALSE; +} + +static void +set_option_off (GtkPrinterOption *option) +{ + /* Any of these will do, _set only applies the value + * if its allowed of the option */ + gtk_printer_option_set (option, "False"); + gtk_printer_option_set (option, "Off"); + gtk_printer_option_set (option, "None"); +} + +static gboolean +value_is_off (const char *value) +{ + return (strcasecmp (value, "None") == 0 || + strcasecmp (value, "Off") == 0 || + strcasecmp (value, "False") == 0); +} + +static int +available_choices (ppd_file_t *ppd, + ppd_option_t *option, + ppd_choice_t ***available, + gboolean keep_if_only_one_option) +{ + ppd_option_t *other_option; + int i, j; + char *conflicts; + ppd_const_t *constraint; + const char *choice, *other_choice; + ppd_option_t *option1, *option2; + ppd_group_t *installed_options; + int num_conflicts; + gboolean all_default; + int add_auto; + + if (available) + *available = NULL; + + conflicts = g_new0 (char, option->num_choices); + + installed_options = NULL; + for (i = 0; i < ppd->num_groups; i++) + { + if (strcmp (ppd->groups[i].name, "InstallableOptions") == 0) + { + installed_options = &ppd->groups[i]; + break; + } + } + + for (i = ppd->num_consts, constraint = ppd->consts; i > 0; i--, constraint++) + { + option1 = ppdFindOption (ppd, constraint->option1); + if (option1 == NULL) + continue; + + option2 = ppdFindOption (ppd, constraint->option2); + if (option2 == NULL) + continue; + + if (option == option1) + { + choice = constraint->choice1; + other_option = option2; + other_choice = constraint->choice2; + } + else if (option == option2) + { + choice = constraint->choice2; + other_option = option1; + other_choice = constraint->choice1; + } + else + continue; + + /* We only care of conflicts with installed_options and + PageSize */ + if (!group_has_option (installed_options, other_option) && + (strcmp (other_option->keyword, "PageSize") != 0)) + continue; + + if (*other_choice == 0) + { + /* Conflict only if the installed option is not off */ + if (value_is_off (other_option->defchoice)) + continue; + } + /* Conflict if the installed option has the specified default */ + else if (strcasecmp (other_choice, other_option->defchoice) != 0) + continue; + + if (*choice == 0) + { + /* Conflict with all non-off choices */ + for (j = 0; j < option->num_choices; j++) + { + if (!value_is_off (option->choices[j].choice)) + conflicts[j] = 1; + } + } + else + { + for (j = 0; j < option->num_choices; j++) + { + if (strcasecmp (option->choices[j].choice, choice) == 0) + conflicts[j] = 1; + } + } + } + + num_conflicts = 0; + all_default = TRUE; + for (j = 0; j < option->num_choices; j++) + { + if (conflicts[j]) + num_conflicts++; + else if (strcmp (option->choices[j].choice, option->defchoice) != 0) + all_default = FALSE; + } + + if (all_default && !keep_if_only_one_option) + return 0; + + if (num_conflicts == option->num_choices) + return 0; + + + /* Some ppds don't have a "use printer default" option for + InputSlot. This means you always have to select a particular slot, + and you can't auto-pick source based on the paper size. To support + this we always add an auto option if there isn't one already. If + the user chooses the generated option we don't send any InputSlot + value when printing. The way we detect existing auto-cases is based + on feedback from Michael Sweet of cups fame. + */ + add_auto = 0; + if (strcmp (option->keyword, "InputSlot") == 0) + { + gboolean found_auto = FALSE; + for (j = 0; j < option->num_choices; j++) + { + if (!conflicts[j]) + { + if (strcmp (option->choices[j].choice, "Auto") == 0 || + strcmp (option->choices[j].choice, "AutoSelect") == 0 || + strcmp (option->choices[j].choice, "Default") == 0 || + strcmp (option->choices[j].choice, "None") == 0 || + strcmp (option->choices[j].choice, "PrinterDefault") == 0 || + strcmp (option->choices[j].choice, "Unspecified") == 0 || + option->choices[j].code == NULL || + option->choices[j].code[0] == 0) + { + found_auto = TRUE; + break; + } + } + } + + if (!found_auto) + add_auto = 1; + } + + if (available) + { + + *available = g_new (ppd_choice_t *, option->num_choices - num_conflicts + add_auto); + + i = 0; + for (j = 0; j < option->num_choices; j++) + { + if (!conflicts[j]) + (*available)[i++] = &option->choices[j]; + } + + if (add_auto) + (*available)[i++] = NULL; + } + + return option->num_choices - num_conflicts + add_auto; +} + +static GtkPrinterOption * +create_pickone_option (ppd_file_t *ppd_file, + ppd_option_t *ppd_option, + const char *gtk_name) +{ + GtkPrinterOption *option; + ppd_choice_t **available; + char *label; + int n_choices; + int i; + + g_assert (ppd_option->ui == PPD_UI_PICKONE); + + option = NULL; + + n_choices = available_choices (ppd_file, ppd_option, &available, g_str_has_prefix (gtk_name, "gtk-")); + if (n_choices > 0) + { + label = get_option_text (ppd_file, ppd_option); + option = gtk_printer_option_new (gtk_name, label, + GTK_PRINTER_OPTION_TYPE_PICKONE); + g_free (label); + + gtk_printer_option_allocate_choices (option, n_choices); + for (i = 0; i < n_choices; i++) + { + if (available[i] == NULL) + { + /* This was auto-added */ + option->choices[i] = g_strdup ("gtk-ignore-value"); + option->choices_display[i] = g_strdup (_("Printer Default")); + } + else + { + option->choices[i] = g_strdup (available[i]->choice); + option->choices_display[i] = get_choice_text (ppd_file, available[i]); + } + } + gtk_printer_option_set (option, ppd_option->defchoice); + } +#ifdef PRINT_IGNORED_OPTIONS + else + g_warning ("Ignoring pickone %s\n", ppd_option->text); +#endif + g_free (available); + + return option; +} + +static GtkPrinterOption * +create_boolean_option (ppd_file_t *ppd_file, + ppd_option_t *ppd_option, + const char *gtk_name) +{ + GtkPrinterOption *option; + ppd_choice_t **available; + char *label; + int n_choices; + + g_assert (ppd_option->ui == PPD_UI_BOOLEAN); + + option = NULL; + + n_choices = available_choices (ppd_file, ppd_option, &available, g_str_has_prefix (gtk_name, "gtk-")); + if (n_choices == 2) + { + label = get_option_text (ppd_file, ppd_option); + option = gtk_printer_option_new (gtk_name, label, + GTK_PRINTER_OPTION_TYPE_BOOLEAN); + g_free (label); + + gtk_printer_option_allocate_choices (option, 2); + option->choices[0] = g_strdup ("True"); + option->choices_display[0] = g_strdup ("True"); + option->choices[1] = g_strdup ("True"); + option->choices_display[1] = g_strdup ("True"); + + gtk_printer_option_set (option, ppd_option->defchoice); + } +#ifdef PRINT_IGNORED_OPTIONS + else + g_warning ("Ignoring boolean %s\n", ppd_option->text); +#endif + g_free (available); + + return option; +} + +static char * +get_option_name (const char *keyword) +{ + int i; + + for (i = 0; i < G_N_ELEMENTS (option_names); i++) + if (strcmp (option_names[i].ppd_keyword, keyword) == 0) + return g_strdup (option_names[i].name); + + return g_strdup_printf ("cups-%s", keyword); +} + +static int +strptr_cmp (const void *a, const void *b) +{ + char **aa = (char **)a; + char **bb = (char **)b; + return strcmp (*aa, *bb); +} + + +static gboolean +string_in_table (char *str, const char *table[], int table_len) +{ + return bsearch (&str, table, table_len, sizeof (char *), (void *)strptr_cmp) != NULL; +} + +#define STRING_IN_TABLE(_str, _table) (string_in_table (_str, _table, G_N_ELEMENTS (_table))) + +static void +handle_option (GtkPrinterOptionSet *set, + ppd_file_t *ppd_file, + ppd_option_t *ppd_option, + ppd_group_t *toplevel_group, + GtkPrintSettings *settings) +{ + GtkPrinterOption *option; + char *name; + + if (STRING_IN_TABLE (ppd_option->keyword, cups_option_blacklist)) + return; + + name = get_option_name (ppd_option->keyword); + + option = NULL; + if (ppd_option->ui == PPD_UI_PICKONE) + { + option = create_pickone_option (ppd_file, ppd_option, name); + } + else if (ppd_option->ui == PPD_UI_BOOLEAN) + { + option = create_boolean_option (ppd_file, ppd_option, name); + } + else + g_warning ("Ignored pickmany setting %s\n", ppd_option->text); + + + if (option) + { + if (STRING_IN_TABLE (toplevel_group->name, + color_group_whitelist) || + STRING_IN_TABLE (ppd_option->keyword, + color_option_whitelist)) + { + option->group = g_strdup ("ColorPage"); + } + else if (STRING_IN_TABLE (toplevel_group->name, + image_quality_group_whitelist) || + STRING_IN_TABLE (ppd_option->keyword, + image_quality_option_whitelist)) + { + option->group = g_strdup ("ImageQualityPage"); + } + else if (STRING_IN_TABLE (toplevel_group->name, + finishing_group_whitelist) || + STRING_IN_TABLE (ppd_option->keyword, + finishing_option_whitelist)) + { + option->group = g_strdup ("FinishingPage"); + } + else + { + option->group = g_strdup (toplevel_group->text); + } + + set_option_from_settings (option, settings); + + gtk_printer_option_set_add (set, option); + } + + g_free (name); +} + +static void +handle_group (GtkPrinterOptionSet *set, + ppd_file_t *ppd_file, + ppd_group_t *group, + ppd_group_t *toplevel_group, + GtkPrintSettings *settings) +{ + int i; + + /* Ignore installable options */ + if (strcmp (toplevel_group->name, "InstallableOptions") == 0) + return; + + for (i = 0; i < group->num_options; i++) + handle_option (set, ppd_file, &group->options[i], toplevel_group, settings); + + for (i = 0; i < group->num_subgroups; i++) + handle_group (set, ppd_file, &group->subgroups[i], toplevel_group, settings); + +} + +static GtkPrinterOptionSet * +cups_printer_get_options (GtkPrinter *printer, + GtkPrintSettings *settings, + GtkPageSetup *page_setup) +{ + GtkPrinterOptionSet *set; + GtkPrinterOption *option; + ppd_file_t *ppd_file; + int i; + char *print_at[] = { "now", "at", "on-hold" }; + char *n_up[] = {"1", "2", "4", "6", "9", "16" }; + char *prio[] = {"100", "80", "50", "30" }; + char *prio_display[] = {N_("Urgent"), N_("High"), N_("Medium"), N_("Low") }; + char *cover[] = {"none", "classified", "confidential", "secret", "standard", "topsecret", "unclassified" }; + char *cover_display[] = {N_("None"), N_("Classified"), N_("Confidential"), N_("Secret"), N_("Standard"), N_("Top Secret"), N_("Unclassified"),}; + + + set = gtk_printer_option_set_new (); + + /* Cups specific, non-ppd related settings */ + + option = gtk_printer_option_new ("gtk-n-up", "Pages Per Sheet", GTK_PRINTER_OPTION_TYPE_PICKONE); + gtk_printer_option_choices_from_array (option, G_N_ELEMENTS (n_up), + n_up, n_up); + gtk_printer_option_set (option, "1"); + set_option_from_settings (option, settings); + gtk_printer_option_set_add (set, option); + g_object_unref (option); + + for (i = 0; i < G_N_ELEMENTS(prio_display); i++) + prio_display[i] = _(prio_display[i]); + + option = gtk_printer_option_new ("gtk-job-prio", "Job Priority", GTK_PRINTER_OPTION_TYPE_PICKONE); + gtk_printer_option_choices_from_array (option, G_N_ELEMENTS (prio), + prio, prio_display); + gtk_printer_option_set (option, "50"); + set_option_from_settings (option, settings); + gtk_printer_option_set_add (set, option); + g_object_unref (option); + + option = gtk_printer_option_new ("gtk-billing-info", "Billing Info", GTK_PRINTER_OPTION_TYPE_STRING); + gtk_printer_option_set (option, ""); + set_option_from_settings (option, settings); + gtk_printer_option_set_add (set, option); + g_object_unref (option); + + for (i = 0; i < G_N_ELEMENTS(cover_display); i++) + cover_display[i] = _(cover_display[i]); + + option = gtk_printer_option_new ("gtk-cover-before", "Before", GTK_PRINTER_OPTION_TYPE_PICKONE); + gtk_printer_option_choices_from_array (option, G_N_ELEMENTS (cover), + cover, cover_display); + gtk_printer_option_set (option, "none"); + set_option_from_settings (option, settings); + gtk_printer_option_set_add (set, option); + g_object_unref (option); + + option = gtk_printer_option_new ("gtk-cover-after", "After", GTK_PRINTER_OPTION_TYPE_PICKONE); + gtk_printer_option_choices_from_array (option, G_N_ELEMENTS (cover), + cover, cover_display); + gtk_printer_option_set (option, "none"); + set_option_from_settings (option, settings); + gtk_printer_option_set_add (set, option); + g_object_unref (option); + + option = gtk_printer_option_new ("gtk-print-time", "Print at", GTK_PRINTER_OPTION_TYPE_PICKONE); + gtk_printer_option_choices_from_array (option, G_N_ELEMENTS (print_at), + print_at, print_at); + gtk_printer_option_set (option, "now"); + set_option_from_settings (option, settings); + gtk_printer_option_set_add (set, option); + g_object_unref (option); + + option = gtk_printer_option_new ("gtk-print-time-text", "Print at time", GTK_PRINTER_OPTION_TYPE_STRING); + gtk_printer_option_set (option, ""); + set_option_from_settings (option, settings); + gtk_printer_option_set_add (set, option); + g_object_unref (option); + + /* Printer (ppd) specific settings */ + ppd_file = gtk_printer_cups_get_ppd (GTK_PRINTER_CUPS (printer)); + if (ppd_file) + { + GtkPaperSize *paper_size; + ppd_option_t *option; + + ppdMarkDefaults (ppd_file); + + paper_size = gtk_page_setup_get_paper_size (page_setup); + + option = ppdFindOption(ppd_file, "PageSize"); + strncpy (option->defchoice, gtk_paper_size_get_ppd_name (paper_size), + PPD_MAX_NAME); + + for (i = 0; i < ppd_file->num_groups; i++) + handle_group (set, ppd_file, &ppd_file->groups[i], &ppd_file->groups[i], settings); + } + + return set; +} + + +static void +mark_option_from_set (GtkPrinterOptionSet *set, + ppd_file_t *ppd_file, + ppd_option_t *ppd_option) +{ + GtkPrinterOption *option; + char *name = get_option_name (ppd_option->keyword); + + option = gtk_printer_option_set_lookup (set, name); + + if (option) + ppdMarkOption (ppd_file, ppd_option->keyword, option->value); + + g_free (name); +} + + +static void +mark_group_from_set (GtkPrinterOptionSet *set, + ppd_file_t *ppd_file, + ppd_group_t *group) +{ + int i; + + for (i = 0; i < group->num_options; i++) + mark_option_from_set (set, ppd_file, &group->options[i]); + + for (i = 0; i < group->num_subgroups; i++) + mark_group_from_set (set, ppd_file, &group->subgroups[i]); +} + +static void +set_conflicts_from_option (GtkPrinterOptionSet *set, + ppd_file_t *ppd_file, + ppd_option_t *ppd_option) +{ + GtkPrinterOption *option; + char *name; + if (ppd_option->conflicted) + { + name = get_option_name (ppd_option->keyword); + option = gtk_printer_option_set_lookup (set, name); + + if (option) + gtk_printer_option_set_has_conflict (option, TRUE); + else + g_warning ("conflict for option %s ignored", ppd_option->keyword); + + g_free (name); + } +} + +static void +set_conflicts_from_group (GtkPrinterOptionSet *set, + ppd_file_t *ppd_file, + ppd_group_t *group) +{ + int i; + + for (i = 0; i < group->num_options; i++) + set_conflicts_from_option (set, ppd_file, &group->options[i]); + + for (i = 0; i < group->num_subgroups; i++) + set_conflicts_from_group (set, ppd_file, &group->subgroups[i]); +} + +static gboolean +cups_printer_mark_conflicts (GtkPrinter *printer, + GtkPrinterOptionSet *options) +{ + ppd_file_t *ppd_file; + int num_conflicts; + int i; + + ppd_file = gtk_printer_cups_get_ppd (GTK_PRINTER_CUPS (printer)); + + if (ppd_file == NULL) + return FALSE; + + ppdMarkDefaults (ppd_file); + + for (i = 0; i < ppd_file->num_groups; i++) + mark_group_from_set (options, ppd_file, &ppd_file->groups[i]); + + num_conflicts = ppdConflicts (ppd_file); + + if (num_conflicts > 0) + { + for (i = 0; i < ppd_file->num_groups; i++) + set_conflicts_from_group (options, ppd_file, &ppd_file->groups[i]); + } + + return num_conflicts > 0; +} + +struct OptionData { + GtkPrinter *printer; + GtkPrinterOptionSet *options; + GtkPrintSettings *settings; + ppd_file_t *ppd_file; +}; + +typedef struct { + const char *cups; + const char *standard; +} NameMapping; + +static void +map_settings_to_option (GtkPrinterOption *option, + const NameMapping table[], + int n_elements, + GtkPrintSettings *settings, + const char *standard_name, + const char *cups_name) +{ + int i; + char *name; + const char *cups_value; + const char *standard_value; + + /* If the cups-specific setting is set, always use that */ + + name = g_strdup_printf ("cups-%s", cups_name); + cups_value = gtk_print_settings_get (settings, name); + g_free (name); + + if (cups_value != NULL) { + gtk_printer_option_set (option, cups_value); + return; + } + + /* Otherwise we try to convert from the general setting */ + standard_value = gtk_print_settings_get (settings, standard_name); + if (standard_value == NULL) + return; + + for (i = 0; i < n_elements; i++) + { + if (table[i].cups == NULL && table[i].standard == NULL) + { + gtk_printer_option_set (option, standard_value); + break; + } + else if (table[i].cups == NULL && + strcmp (table[i].standard, standard_value) == 0) + { + set_option_off (option); + break; + } + else if (strcmp (table[i].standard, standard_value) == 0) + { + gtk_printer_option_set (option, table[i].cups); + break; + } + } +} + +static void +map_option_to_settings (const char *value, + const NameMapping table[], + int n_elements, + GtkPrintSettings *settings, + const char *standard_name, + const char *cups_name) +{ + int i; + char *name; + + for (i = 0; i < n_elements; i++) + { + if (table[i].cups == NULL && table[i].standard == NULL) + { + gtk_print_settings_set (settings, + standard_name, + value); + break; + } + else if (table[i].cups == NULL && table[i].standard != NULL) + { + if (value_is_off (value)) + { + gtk_print_settings_set (settings, + standard_name, + table[i].standard); + break; + } + } + else if (strcmp (table[i].cups, value) == 0) + { + gtk_print_settings_set (settings, + standard_name, + table[i].standard); + break; + } + } + + /* Always set the corresponding cups-specific setting */ + name = g_strdup_printf ("cups-%s", cups_name); + gtk_print_settings_set (settings, name, value); + g_free (name); +} + + +static const NameMapping paper_source_map[] = { + { "Lower", "lower"}, + { "Middle", "middle"}, + { "Upper", "upper"}, + { "Rear", "rear"}, + { "Envelope", "envelope"}, + { "Cassette", "cassette"}, + { "LargeCapacity", "large-capacity"}, + { "AnySmallFormat", "small-format"}, + { "AnyLargeFormat", "large-format"}, + { NULL, NULL} +}; + +static const NameMapping output_tray_map[] = { + { "Upper", "upper"}, + { "Lower", "lower"}, + { "Rear", "rear"}, + { NULL, NULL} +}; + +static const NameMapping duplex_map[] = { + { "DuplexTumble", "vertical" }, + { "DuplexNoTumble", "horizontal" }, + { NULL, "simplex" } +}; + +static const NameMapping output_mode_map[] = { + { "Standard", "normal" }, + { "Normal", "normal" }, + { "Draft", "draft" }, + { "Fast", "draft" }, +}; + +static const NameMapping media_type_map[] = { + { "Transparency", "transparency"}, + { "Standard", "stationery"}, + { NULL, NULL} +}; + +static const NameMapping all_map[] = { + { NULL, NULL} +}; + + +static void +set_option_from_settings (GtkPrinterOption *option, + GtkPrintSettings *settings) +{ + const char *cups_value; + char *value; + + if (settings == NULL) + return; + + if (strcmp (option->name, "gtk-paper-source") == 0) + map_settings_to_option (option, paper_source_map, G_N_ELEMENTS (paper_source_map), + settings, GTK_PRINT_SETTINGS_DEFAULT_SOURCE, "InputSlot"); + else if (strcmp (option->name, "gtk-output-tray") == 0) + map_settings_to_option (option, output_tray_map, G_N_ELEMENTS (output_tray_map), + settings, GTK_PRINT_SETTINGS_OUTPUT_BIN, "OutputBin"); + else if (strcmp (option->name, "gtk-duplex") == 0) + map_settings_to_option (option, duplex_map, G_N_ELEMENTS (duplex_map), + settings, GTK_PRINT_SETTINGS_DUPLEX, "Duplex"); + else if (strcmp (option->name, "cups-OutputMode") == 0) + map_settings_to_option (option, output_mode_map, G_N_ELEMENTS (output_mode_map), + settings, GTK_PRINT_SETTINGS_QUALITY, "OutputMode"); + else if (strcmp (option->name, "cups-Resolution") == 0) + { + cups_value = gtk_print_settings_get (settings, option->name); + if (cups_value) + gtk_printer_option_set (option, cups_value); + else + { + int res = gtk_print_settings_get_resolution (settings); + if (res != 0) + { + value = g_strdup_printf ("%ddpi", res); + gtk_printer_option_set (option, value); + g_free (value); + } + } + } + else if (strcmp (option->name, "gtk-paper-type") == 0) + map_settings_to_option (option, media_type_map, G_N_ELEMENTS (media_type_map), + settings, GTK_PRINT_SETTINGS_MEDIA_TYPE, "MediaType"); + else if (strcmp (option->name, "gtk-n-up") == 0) + { + map_settings_to_option (option, all_map, G_N_ELEMENTS (all_map), + settings, GTK_PRINT_SETTINGS_NUMBER_UP, "number-up"); + } + else if (strcmp (option->name, "gtk-billing-info") == 0) + { + cups_value = gtk_print_settings_get (settings, "cups-job-billing"); + if (cups_value) + gtk_printer_option_set (option, cups_value); + } + else if (strcmp (option->name, "gtk-job-prio") == 0) + { + cups_value = gtk_print_settings_get (settings, "cups-job-priority"); + if (cups_value) + gtk_printer_option_set (option, cups_value); + } + else if (strcmp (option->name, "gtk-cover-before") == 0) + { + cups_value = gtk_print_settings_get (settings, "cover-before"); + if (cups_value) + gtk_printer_option_set (option, cups_value); + } + else if (strcmp (option->name, "gtk-cover-after") == 0) + { + cups_value = gtk_print_settings_get (settings, "cover-after"); + if (cups_value) + gtk_printer_option_set (option, cups_value); + } + else if (strcmp (option->name, "gtk-print-time") == 0) + { + cups_value = gtk_print_settings_get (settings, "print-at"); + if (cups_value) + gtk_printer_option_set (option, cups_value); + } + else if (strcmp (option->name, "gtk-print-time-text") == 0) + { + cups_value = gtk_print_settings_get (settings, "print-at-time"); + if (cups_value) + gtk_printer_option_set (option, cups_value); + } + else if (g_str_has_prefix (option->name, "cups-")) + { + cups_value = gtk_print_settings_get (settings, option->name); + if (cups_value) + gtk_printer_option_set (option, cups_value); + } +} + +static void +foreach_option_get_settings (GtkPrinterOption *option, + gpointer user_data) +{ + struct OptionData *data = user_data; + GtkPrintSettings *settings = data->settings; + const char *value; + + value = option->value; + + if (strcmp (option->name, "gtk-paper-source") == 0) + map_option_to_settings (value, paper_source_map, G_N_ELEMENTS (paper_source_map), + settings, GTK_PRINT_SETTINGS_DEFAULT_SOURCE, "InputSlot"); + else if (strcmp (option->name, "gtk-output-tray") == 0) + map_option_to_settings (value, output_tray_map, G_N_ELEMENTS (output_tray_map), + settings, GTK_PRINT_SETTINGS_OUTPUT_BIN, "OutputBin"); + else if (strcmp (option->name, "gtk-duplex") == 0) + map_option_to_settings (value, duplex_map, G_N_ELEMENTS (duplex_map), + settings, GTK_PRINT_SETTINGS_DUPLEX, "Duplex"); + else if (strcmp (option->name, "cups-OutputMode") == 0) + map_option_to_settings (value, output_mode_map, G_N_ELEMENTS (output_mode_map), + settings, GTK_PRINT_SETTINGS_QUALITY, "OutputMode"); + else if (strcmp (option->name, "cups-Resolution") == 0) + { + int res = atoi (value); + /* TODO: What if resolution is on XXXxYYYdpi form? */ + if (res != 0) + gtk_print_settings_set_resolution (settings, res); + gtk_print_settings_set (settings, option->name, value); + } + else if (strcmp (option->name, "gtk-paper-type") == 0) + map_option_to_settings (value, media_type_map, G_N_ELEMENTS (media_type_map), + settings, GTK_PRINT_SETTINGS_MEDIA_TYPE, "MediaType"); + else if (strcmp (option->name, "gtk-n-up") == 0) + map_option_to_settings (value, all_map, G_N_ELEMENTS (all_map), + settings, GTK_PRINT_SETTINGS_NUMBER_UP, "number-up"); + else if (strcmp (option->name, "gtk-billing-info") == 0 && strlen (value) > 0) + gtk_print_settings_set (settings, "cups-job-billing", value); + else if (strcmp (option->name, "gtk-job-prio") == 0) + gtk_print_settings_set (settings, "cups-job-priority", value); + else if (strcmp (option->name, "gtk-cover-before") == 0) + gtk_print_settings_set (settings, "cover-before", value); + else if (strcmp (option->name, "gtk-cover-after") == 0) + gtk_print_settings_set (settings, "cover-after", value); + else if (strcmp (option->name, "gtk-print-time") == 0) + gtk_print_settings_set (settings, "print-at", value); + else if (strcmp (option->name, "gtk-print-time-text") == 0) + gtk_print_settings_set (settings, "print-at-time", value); + else if (g_str_has_prefix (option->name, "cups-")) + gtk_print_settings_set (settings, option->name, value); +} + +static void +cups_printer_get_settings_from_options (GtkPrinter *printer, + GtkPrinterOptionSet *options, + GtkPrintSettings *settings) +{ + struct OptionData data; + const char *print_at, *print_at_time; + + data.printer = printer; + data.options = options; + data.settings = settings; + data.ppd_file = gtk_printer_cups_get_ppd (GTK_PRINTER_CUPS (printer)); + + if (data.ppd_file != NULL) + { + GtkPrinterOption *cover_before, *cover_after; + + gtk_printer_option_set_foreach (options, foreach_option_get_settings, &data); + + cover_before = gtk_printer_option_set_lookup (options, "gtk-cover-before"); + cover_after = gtk_printer_option_set_lookup (options, "gtk-cover-after"); + if (cover_before && cover_after) + { + char *value = g_strdup_printf ("%s,%s", cover_before->value, cover_after->value); + gtk_print_settings_set (settings, "cups-job-sheets", value); + g_free (value); + } + + print_at = gtk_print_settings_get (settings, "print-at"); + print_at_time = gtk_print_settings_get (settings, "print-at-time"); + if (strcmp (print_at, "at") == 0) + gtk_print_settings_set (settings, "cups-job-hold-until", print_at_time); + else if (strcmp (print_at, "on-hold") == 0) + gtk_print_settings_set (settings, "cups-job-hold-until", "indefinite"); + } +} + +static void +cups_printer_prepare_for_print (GtkPrinter *printer, + GtkPrintJob *print_job, + GtkPrintSettings *settings, + GtkPageSetup *page_setup) +{ + GtkPageSet page_set; + GtkPaperSize *paper_size; + const char *ppd_paper_name; + double scale; + + print_job->print_pages = gtk_print_settings_get_print_pages (settings); + print_job->page_ranges = NULL; + print_job->num_page_ranges = 0; + + if (print_job->print_pages == GTK_PRINT_PAGES_RANGES) + print_job->page_ranges = + gtk_print_settings_get_page_ranges (settings, + &print_job->num_page_ranges); + + if (gtk_print_settings_get_collate (settings)) + gtk_print_settings_set (settings, "cups-Collate", "True"); + print_job->collate = FALSE; + + if (gtk_print_settings_get_reverse (settings)) + gtk_print_settings_set (settings, "cups-OutputOrder", "Reverse"); + print_job->reverse = FALSE; + + if (gtk_print_settings_get_num_copies (settings) > 1) + gtk_print_settings_set_int (settings, "cups-copies", + gtk_print_settings_get_num_copies (settings)); + print_job->num_copies = 1; + + scale = gtk_print_settings_get_scale (settings); + print_job->scale = 1.0; + if (scale != 100.0) + print_job->scale = scale/100.0; + + page_set = gtk_print_settings_get_page_set (settings); + if (page_set == GTK_PAGE_SET_EVEN) + gtk_print_settings_set (settings, "cups-page-set", "even"); + else if (page_set == GTK_PAGE_SET_ODD) + gtk_print_settings_set (settings, "cups-page-set", "odd"); + print_job->page_set = GTK_PAGE_SET_ALL; + + paper_size = gtk_page_setup_get_paper_size (page_setup); + ppd_paper_name = gtk_paper_size_get_ppd_name (paper_size); + if (ppd_paper_name != NULL) + gtk_print_settings_set (settings, "cups-PageSize", ppd_paper_name); + else + { + char *custom_name = g_strdup_printf ("Custom.%2fx%.2f", + gtk_paper_size_get_width (paper_size, GTK_UNIT_POINTS), + gtk_paper_size_get_height (paper_size, GTK_UNIT_POINTS)); + gtk_print_settings_set (settings, "cups-PageSize", custom_name); + g_free (custom_name); + } + + print_job->rotate_to_orientation = TRUE; +} + +static GList * +cups_printer_list_papers (GtkPrinter *printer) +{ + ppd_file_t *ppd_file; + ppd_size_t *size; + char *display_name; + GtkPageSetup *page_setup; + GtkPaperSize *paper_size; + ppd_option_t *option; + ppd_choice_t *choice; + GList *l; + int i; + + ppd_file = gtk_printer_cups_get_ppd (GTK_PRINTER_CUPS (printer)); + if (ppd_file == NULL) + return NULL; + + l = NULL; + + for (i = 0; i < ppd_file->num_sizes; i++) + { + size = &ppd_file->sizes[i]; + + display_name = NULL; + option = ppdFindOption(ppd_file, "PageSize"); + if (option) + { + choice = ppdFindChoice(option, size->name); + if (choice) + display_name = ppd_text_to_utf8 (ppd_file, choice->text); + } + if (display_name == NULL) + display_name = g_strdup (size->name); + + page_setup = gtk_page_setup_new (); + paper_size = gtk_paper_size_new_from_ppd (size->name, + display_name, + size->width, + size->length); + gtk_page_setup_set_paper_size (page_setup, paper_size); + gtk_paper_size_free (paper_size); + + gtk_page_setup_set_top_margin (page_setup, size->length - size->top, GTK_UNIT_POINTS); + gtk_page_setup_set_bottom_margin (page_setup, size->bottom, GTK_UNIT_POINTS); + gtk_page_setup_set_left_margin (page_setup, size->left, GTK_UNIT_POINTS); + gtk_page_setup_set_right_margin (page_setup, size->width - size->right, GTK_UNIT_POINTS); + + g_free (display_name); + + l = g_list_prepend (l, page_setup); + } + + return g_list_reverse (l); +} + +static void +cups_printer_get_hard_margins (GtkPrinter *printer, + double *top, + double *bottom, + double *left, + double *right) +{ + ppd_file_t *ppd_file; + + ppd_file = gtk_printer_cups_get_ppd (GTK_PRINTER_CUPS (printer)); + if (ppd_file == NULL) + return; + + *left = ppd_file->custom_margins[0]; + *bottom = ppd_file->custom_margins[1]; + *right = ppd_file->custom_margins[2]; + *top = ppd_file->custom_margins[3]; +} diff --git a/modules/printbackends/cups/gtkprintbackendcups.h b/modules/printbackends/cups/gtkprintbackendcups.h new file mode 100644 index 0000000000..b1e136970a --- /dev/null +++ b/modules/printbackends/cups/gtkprintbackendcups.h @@ -0,0 +1,42 @@ +/* GTK - The GIMP Toolkit + * gtkprintbackendcups.h: Default implementation of GtkPrintBackend for the Common Unix Print System (CUPS) + * Copyright (C) 2003, Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser 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. + */ + +#ifndef __GTK_PRINT_BACKEND_CUPS_H__ +#define __GTK_PRINT_BACKEND_CUPS_H__ + +#include <glib-object.h> +#include "gtkprintbackend.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_PRINT_BACKEND_CUPS (gtk_print_backend_cups_get_type ()) +#define GTK_PRINT_BACKEND_CUPS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_PRINT_BACKEND_CUPS, GtkPrintBackendCups)) +#define GTK_IS_PRINT_BACKEND_CUPS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_PRINT_BACKEND_CUPS)) + +typedef struct _GtkPrintBackendCups GtkPrintBackendCups; + +GtkPrintBackend *gtk_print_backend_cups_new (void); +GType gtk_print_backend_cups_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* __GTK_PRINT_BACKEND_CUPS_H__ */ + + diff --git a/modules/printbackends/cups/gtkprintercups.c b/modules/printbackends/cups/gtkprintercups.c new file mode 100644 index 0000000000..c36b8077e5 --- /dev/null +++ b/modules/printbackends/cups/gtkprintercups.c @@ -0,0 +1,125 @@ +/* GtkPrinterCupsCups + * Copyright (C) 2006 John (J5) Palmieri <johnp@redhat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser 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. + */ + +#include "config.h" +#include "gtkprintercups.h" + +static void gtk_printer_cups_init (GtkPrinterCups *printer); +static void gtk_printer_cups_class_init (GtkPrinterCupsClass *class); +static void gtk_printer_cups_finalize (GObject *object); + +static GtkPrinterClass *gtk_printer_cups_parent_class; +static GType gtk_printer_cups_type = 0; + +void +gtk_printer_cups_register_type (GTypeModule *module) +{ + static const GTypeInfo object_info = + { + sizeof (GtkPrinterCupsClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gtk_printer_cups_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GtkPrinterCups), + 0, /* n_preallocs */ + (GInstanceInitFunc) gtk_printer_cups_init, + }; + + gtk_printer_cups_type = g_type_module_register_type (module, + GTK_TYPE_PRINTER, + "GtkPrinterCups", + &object_info, 0); +} + +GType +gtk_printer_cups_get_type (void) +{ + return gtk_printer_cups_type; +} + +static void +gtk_printer_cups_class_init (GtkPrinterCupsClass *class) +{ + GObjectClass *object_class = (GObjectClass *) class; + + gtk_printer_cups_parent_class = g_type_class_peek_parent (class); + + object_class->finalize = gtk_printer_cups_finalize; +} + +static void +gtk_printer_cups_init (GtkPrinterCups *printer) +{ + printer->device_uri = NULL; + printer->printer_uri = NULL; + printer->state = 0; + printer->hostname = NULL; + printer->port = 0; + printer->ppd_file = NULL; +} + +static void +gtk_printer_cups_finalize (GObject *object) +{ + g_return_if_fail (object != NULL); + + GtkPrinterCups *printer = GTK_PRINTER_CUPS (object); + + g_free (printer->device_uri); + g_free (printer->printer_uri); + g_free (printer->hostname); + + if (printer->ppd_file) + ppdClose (printer->ppd_file); + + if (G_OBJECT_CLASS (gtk_printer_cups_parent_class)->finalize) + G_OBJECT_CLASS (gtk_printer_cups_parent_class)->finalize (object); +} + +/** + * gtk_printer_cups_new: + * + * Creates a new #GtkPrinterCups. + * + * Return value: a new #GtkPrinterCups + * + * Since: 2.10 + **/ +GtkPrinterCups * +gtk_printer_cups_new (const char *name, + GtkPrintBackend *backend) +{ + GObject *result; + + result = g_object_new (GTK_TYPE_PRINTER_CUPS, + "name", name, + "backend", backend, + "is-virtual", FALSE, + NULL); + + return (GtkPrinterCups *) result; +} + +ppd_file_t * +gtk_printer_cups_get_ppd (GtkPrinterCups *printer) +{ + return printer->ppd_file; +} diff --git a/modules/printbackends/cups/gtkprintercups.h b/modules/printbackends/cups/gtkprintercups.h new file mode 100644 index 0000000000..510050918f --- /dev/null +++ b/modules/printbackends/cups/gtkprintercups.h @@ -0,0 +1,70 @@ +/* GtkPrinterCups + * Copyright (C) 2006 John (J5) Palmieri <johnp@redhat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser 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. + */ +#ifndef __GTK_PRINTER_CUPS_H__ +#define __GTK_PRINTER_CUPS_H__ + +#include <glib.h> +#include <glib-object.h> +#include <cups/cups.h> +#include <cups/ppd.h> + +#include "gtkprinter.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_PRINTER_CUPS (gtk_printer_cups_get_type ()) +#define GTK_PRINTER_CUPS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_PRINTER_CUPS, GtkPrinterCups)) +#define GTK_PRINTER_CUPS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_PRINTER_CUPS, GtkPrinterCupsClass)) +#define GTK_IS_PRINTER_CUPS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_PRINTER_CUPS)) +#define GTK_IS_PRINTER_CUPS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_PRINTER_CUPS)) +#define GTK_PRINTER_CUPS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_PRINTER_CUPS, GtkPrinterCupsClass)) + +typedef struct _GtkPrinterCups GtkPrinterCups; +typedef struct _GtkPrinterCupsClass GtkPrinterCupsClass; +typedef struct _GtkPrinterCupsPrivate GtkPrinterCupsPrivate; + +struct _GtkPrinterCups +{ + GtkPrinter parent_instance; + + gchar *device_uri; + gchar *printer_uri; + gchar *hostname; + gint port; + + ipp_pstate_t state; + gboolean reading_ppd; + ppd_file_t *ppd_file; +}; + +struct _GtkPrinterCupsClass +{ + GtkPrinterClass parent_class; + +}; + +GType gtk_printer_cups_get_type (void) G_GNUC_CONST; +void gtk_printer_cups_register_type (GTypeModule *module); +GtkPrinterCups *gtk_printer_cups_new (const char *name, + GtkPrintBackend *backend); +ppd_file_t * gtk_printer_cups_get_ppd (GtkPrinterCups *printer); + +G_END_DECLS + +#endif /* __GTK_PRINTER_CUPS_H__ */ |