diff options
Diffstat (limited to 'panels/printers/pp-utils.c')
-rw-r--r-- | panels/printers/pp-utils.c | 3643 |
1 files changed, 3643 insertions, 0 deletions
diff --git a/panels/printers/pp-utils.c b/panels/printers/pp-utils.c new file mode 100644 index 0000000..127660f --- /dev/null +++ b/panels/printers/pp-utils.c @@ -0,0 +1,3643 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright 2009-2010 Red Hat, Inc, + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "config.h" + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <gtk/gtk.h> +#include <cups/cups.h> +#include <cups/ppd.h> + +#include "pp-utils.h" + +#define DBUS_TIMEOUT 120000 +#define DBUS_TIMEOUT_LONG 600000 + +#if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 5) +#define HAVE_CUPS_1_6 1 +#endif + +#ifndef HAVE_CUPS_1_6 +#define ippGetCount(attr) attr->num_values +#define ippGetGroupTag(attr) attr->group_tag +#define ippGetValueTag(attr) attr->value_tag +#define ippGetName(attr) attr->name +#define ippGetStatusCode(ipp) ipp->request.status.status_code +#define ippGetInteger(attr, element) attr->values[element].integer +#define ippGetString(attr, element, language) attr->values[element].string.text +#define ippGetBoolean(attr, element) attr->values[element].boolean + +static int +ippGetRange (ipp_attribute_t *attr, + int element, + int *upper) +{ + *upper = attr->values[element].range.upper; + return (attr->values[element].range.lower); +} + +static ipp_attribute_t * +ippFirstAttribute (ipp_t *ipp) +{ + if (!ipp) + return (NULL); + return (ipp->current = ipp->attrs); +} + +static ipp_attribute_t * +ippNextAttribute (ipp_t *ipp) +{ + if (!ipp || !ipp->current) + return (NULL); + return (ipp->current = ipp->current->next); +} +#endif + +#if (CUPS_VERSION_MAJOR == 1) && (CUPS_VERSION_MINOR <= 6) +#define HTTP_URI_STATUS_OK HTTP_URI_OK +#endif + +gchar * +get_tag_value (const gchar *tag_string, const gchar *tag_name) +{ + gchar **tag_string_splitted = NULL; + gchar *tag_value = NULL; + gint tag_name_length; + gint i; + + if (tag_string && tag_name) + { + tag_name_length = strlen (tag_name); + tag_string_splitted = g_strsplit (tag_string, ";", 0); + if (tag_string_splitted) + { + for (i = 0; i < g_strv_length (tag_string_splitted); i++) + if (g_ascii_strncasecmp (tag_string_splitted[i], tag_name, tag_name_length) == 0) + if (strlen (tag_string_splitted[i]) > tag_name_length + 1) + tag_value = g_strdup (tag_string_splitted[i] + tag_name_length + 1); + + g_strfreev (tag_string_splitted); + } + } + + return tag_value; +} + + +/* + * Normalize given string so that it is lowercase, doesn't + * have trailing or leading whitespaces and digits doesn't + * neighbour with alphabetic. + * (see cupshelpers/ppds.py from system-config-printer) + */ +static gchar * +normalize (const gchar *input_string) +{ + gchar *result = NULL; + gint i, j = 0, k = -1; + + if (input_string) + { + g_autofree gchar *tmp = g_strstrip (g_ascii_strdown (input_string, -1)); + if (tmp) + { + g_autofree gchar *res = g_new (gchar, 2 * strlen (tmp)); + + for (i = 0; i < strlen (tmp); i++) + { + if ((g_ascii_isalpha (tmp[i]) && k >= 0 && g_ascii_isdigit (res[k])) || + (g_ascii_isdigit (tmp[i]) && k >= 0 && g_ascii_isalpha (res[k]))) + { + res[j] = ' '; + k = j++; + res[j] = tmp[i]; + k = j++; + } + else + { + if (g_ascii_isspace (tmp[i]) || !g_ascii_isalnum (tmp[i])) + { + if (!(k >= 0 && res[k] == ' ')) + { + res[j] = ' '; + k = j++; + } + } + else + { + res[j] = tmp[i]; + k = j++; + } + } + } + + res[j] = '\0'; + + result = g_strdup (res); + } + } + + return result; +} + + +char * +get_dest_attr (const char *dest_name, + const char *attr) +{ + cups_dest_t *dests; + int num_dests; + cups_dest_t *dest; + const char *value; + char *ret; + + if (dest_name == NULL) + return NULL; + + ret = NULL; + + num_dests = cupsGetDests (&dests); + if (num_dests < 1) { + g_debug ("Unable to get printer destinations"); + return NULL; + } + + dest = cupsGetDest (dest_name, NULL, num_dests, dests); + if (dest == NULL) { + g_debug ("Unable to find a printer named '%s'", dest_name); + goto out; + } + + value = cupsGetOption (attr, dest->num_options, dest->options); + if (value == NULL) { + g_debug ("Unable to get %s for '%s'", attr, dest_name); + goto out; + } + ret = g_strdup (value); +out: + cupsFreeDests (num_dests, dests); + + return ret; +} + +gchar * +get_ppd_attribute (const gchar *ppd_file_name, + const gchar *attribute_name) +{ + ppd_file_t *ppd_file = NULL; + ppd_attr_t *ppd_attr = NULL; + gchar *result = NULL; + + if (ppd_file_name) + { + ppd_file = ppdOpenFile (ppd_file_name); + + if (ppd_file) + { + ppd_attr = ppdFindAttr (ppd_file, attribute_name, NULL); + if (ppd_attr != NULL) + result = g_strdup (ppd_attr->value); + ppdClose (ppd_file); + } + } + + return result; +} + +/* Set default destination in ~/.cups/lpoptions. + * Unset default destination if "dest" is NULL. + */ +void +set_local_default_printer (const gchar *printer_name) +{ + cups_dest_t *dests = NULL; + int num_dests = 0; + int i; + + num_dests = cupsGetDests (&dests); + + for (i = 0; i < num_dests; i ++) + { + if (printer_name && g_strcmp0 (dests[i].name, printer_name) == 0) + dests[i].is_default = 1; + else + dests[i].is_default = 0; + } + + cupsSetDests (num_dests, dests); +} + +/* + * This function does something which should be provided by CUPS... + * It returns FALSE if the renaming fails. + */ +gboolean +printer_rename (const gchar *old_name, + const gchar *new_name) +{ + ipp_attribute_t *attr = NULL; + cups_ptype_t printer_type = 0; + cups_dest_t *dests = NULL; + cups_dest_t *dest = NULL; + cups_job_t *jobs = NULL; + g_autoptr(GDBusConnection) bus = NULL; + const gchar *printer_location = NULL; + const gchar *printer_info = NULL; + const gchar *printer_uri = NULL; + const gchar *device_uri = NULL; + const gchar *job_sheets = NULL; + gboolean result = FALSE; + gboolean accepting = TRUE; + gboolean printer_paused = FALSE; + gboolean default_printer = FALSE; + gboolean printer_shared = FALSE; + g_autoptr(GError) error = NULL; + http_t *http; + g_autofree gchar *ppd_link = NULL; + g_autofree gchar *ppd_filename = NULL; + gchar **sheets = NULL; + gchar **users_allowed = NULL; + gchar **users_denied = NULL; + gchar **member_names = NULL; + const gchar *start_sheet = NULL; + const gchar *end_sheet = NULL; + g_autofree gchar *error_policy = NULL; + g_autofree gchar *op_policy = NULL; + ipp_t *request; + ipp_t *response; + gint i; + int num_dests = 0; + int num_jobs = 0; + static const char * const requested_attrs[] = { + "printer-error-policy", + "printer-op-policy", + "requesting-user-name-allowed", + "requesting-user-name-denied", + "member-names"}; + + if (old_name == NULL || + old_name[0] == '\0' || + new_name == NULL || + new_name[0] == '\0' || + g_strcmp0 (old_name, new_name) == 0) + return FALSE; + + num_dests = cupsGetDests (&dests); + + dest = cupsGetDest (new_name, NULL, num_dests, dests); + if (dest) + { + cupsFreeDests (num_dests, dests); + return FALSE; + } + + num_jobs = cupsGetJobs (&jobs, old_name, 0, CUPS_WHICHJOBS_ACTIVE); + cupsFreeJobs (num_jobs, jobs); + if (num_jobs > 1) + { + g_warning ("There are queued jobs on printer %s!", old_name); + cupsFreeDests (num_dests, dests); + return FALSE; + } + + /* + * Gather some informations about the original printer + */ + dest = cupsGetDest (old_name, NULL, num_dests, dests); + if (dest) + { + for (i = 0; i < dest->num_options; i++) + { + if (g_strcmp0 (dest->options[i].name, "printer-is-accepting-jobs") == 0) + accepting = g_strcmp0 (dest->options[i].value, "true") == 0; + else if (g_strcmp0 (dest->options[i].name, "printer-is-shared") == 0) + printer_shared = g_strcmp0 (dest->options[i].value, "true") == 0; + else if (g_strcmp0 (dest->options[i].name, "device-uri") == 0) + device_uri = dest->options[i].value; + else if (g_strcmp0 (dest->options[i].name, "printer-uri-supported") == 0) + printer_uri = dest->options[i].value; + else if (g_strcmp0 (dest->options[i].name, "printer-info") == 0) + printer_info = dest->options[i].value; + else if (g_strcmp0 (dest->options[i].name, "printer-location") == 0) + printer_location = dest->options[i].value; + else if (g_strcmp0 (dest->options[i].name, "printer-state") == 0) + printer_paused = g_strcmp0 (dest->options[i].value, "5") == 0; + else if (g_strcmp0 (dest->options[i].name, "job-sheets") == 0) + job_sheets = dest->options[i].value; + else if (g_strcmp0 (dest->options[i].name, "printer-type") == 0) + printer_type = atoi (dest->options[i].value); + } + default_printer = dest->is_default; + } + cupsFreeDests (num_dests, dests); + + if (accepting) + { + printer_set_accepting_jobs (old_name, FALSE, NULL); + + num_jobs = cupsGetJobs (&jobs, old_name, 0, CUPS_WHICHJOBS_ACTIVE); + cupsFreeJobs (num_jobs, jobs); + if (num_jobs > 1) + { + printer_set_accepting_jobs (old_name, accepting, NULL); + g_warning ("There are queued jobs on printer %s!", old_name); + return FALSE; + } + } + + + /* + * Gather additional informations about the original printer + */ +#ifdef HAVE_CUPS_HTTPCONNECT2 + http = httpConnect2 (cupsServer (), ippPort (), NULL, AF_UNSPEC, + cupsEncryption (), 1, 30000, NULL); +#else + http = httpConnectEncrypt (cupsServer (), ippPort (), cupsEncryption ()); +#endif + if (http != NULL) + { + request = ippNewRequest (IPP_GET_PRINTER_ATTRIBUTES); + ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, + "printer-uri", NULL, printer_uri); + ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, + "requested-attributes", G_N_ELEMENTS (requested_attrs), NULL, requested_attrs); + response = cupsDoRequest (http, request, "/"); + + if (response) + { + if (ippGetStatusCode (response) <= IPP_OK_CONFLICT) + { + attr = ippFindAttribute (response, "printer-error-policy", IPP_TAG_NAME); + if (attr) + error_policy = g_strdup (ippGetString (attr, 0, NULL)); + + attr = ippFindAttribute (response, "printer-op-policy", IPP_TAG_NAME); + if (attr) + op_policy = g_strdup (ippGetString (attr, 0, NULL)); + + attr = ippFindAttribute (response, "requesting-user-name-allowed", IPP_TAG_NAME); + if (attr && ippGetCount (attr) > 0) + { + users_allowed = g_new0 (gchar *, ippGetCount (attr) + 1); + for (i = 0; i < ippGetCount (attr); i++) + users_allowed[i] = g_strdup (ippGetString (attr, i, NULL)); + } + + attr = ippFindAttribute (response, "requesting-user-name-denied", IPP_TAG_NAME); + if (attr && ippGetCount (attr) > 0) + { + users_denied = g_new0 (gchar *, ippGetCount (attr) + 1); + for (i = 0; i < ippGetCount (attr); i++) + users_denied[i] = g_strdup (ippGetString (attr, i, NULL)); + } + + attr = ippFindAttribute (response, "member-names", IPP_TAG_NAME); + if (attr && ippGetCount (attr) > 0) + { + member_names = g_new0 (gchar *, ippGetCount (attr) + 1); + for (i = 0; i < ippGetCount (attr); i++) + member_names[i] = g_strdup (ippGetString (attr, i, NULL)); + } + } + ippDelete (response); + } + httpClose (http); + } + + if (job_sheets) + { + sheets = g_strsplit (job_sheets, ",", 0); + if (g_strv_length (sheets) > 1) + { + start_sheet = sheets[0]; + end_sheet = sheets[1]; + } + } + + ppd_link = g_strdup (cupsGetPPD (old_name)); + if (ppd_link) + { + ppd_filename = g_file_read_link (ppd_link, NULL); + + if (!ppd_filename) + ppd_filename = g_strdup (ppd_link); + } + + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (!bus) + { + g_warning ("Failed to get system bus: %s", error->message); + } + else + { + if (printer_type & CUPS_PRINTER_CLASS) + { + if (member_names) + for (i = 0; i < g_strv_length (member_names); i++) + class_add_printer (new_name, member_names[i]); + } + else + { + g_autoptr(GVariant) output = NULL; + g_autoptr(GError) add_error = NULL; + + output = g_dbus_connection_call_sync (bus, + MECHANISM_BUS, + "/", + MECHANISM_BUS, + "PrinterAddWithPpdFile", + g_variant_new ("(sssss)", + new_name, + device_uri ? device_uri : "", + ppd_filename ? ppd_filename : "", + printer_info ? printer_info : "", + printer_location ? printer_location : ""), + G_VARIANT_TYPE ("(s)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &add_error); + + if (output) + { + const gchar *ret_error; + + g_variant_get (output, "(&s)", &ret_error); + if (ret_error[0] != '\0') + g_warning ("cups-pk-helper: rename of printer %s to %s failed: %s", old_name, new_name, ret_error); + } + else + { + g_warning ("%s", add_error->message); + } + } + } + + if (ppd_link) + { + g_unlink (ppd_link); + } + + num_dests = cupsGetDests (&dests); + dest = cupsGetDest (new_name, NULL, num_dests, dests); + if (dest) + { + printer_set_accepting_jobs (new_name, accepting, NULL); + printer_set_enabled (new_name, !printer_paused); + printer_set_shared (new_name, printer_shared); + printer_set_job_sheets (new_name, start_sheet, end_sheet); + printer_set_policy (new_name, op_policy, FALSE); + printer_set_policy (new_name, error_policy, TRUE); + printer_set_users (new_name, users_allowed, TRUE); + printer_set_users (new_name, users_denied, FALSE); + if (default_printer) + printer_set_default (new_name); + + printer_delete (old_name); + + result = TRUE; + } + else + printer_set_accepting_jobs (old_name, accepting, NULL); + + cupsFreeDests (num_dests, dests); + if (sheets) + g_strfreev (sheets); + if (users_allowed) + g_strfreev (users_allowed); + if (users_denied) + g_strfreev (users_denied); + + return result; +} + +gboolean +printer_set_location (const gchar *printer_name, + const gchar *location) +{ + g_autoptr(GDBusConnection) bus = NULL; + g_autoptr(GVariant) output = NULL; + const gchar *ret_error; + g_autoptr(GError) error = NULL; + + if (!printer_name || !location) + return TRUE; + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (!bus) + { + g_warning ("Failed to get system bus: %s", error->message); + return TRUE; + } + + output = g_dbus_connection_call_sync (bus, + MECHANISM_BUS, + "/", + MECHANISM_BUS, + "PrinterSetLocation", + g_variant_new ("(ss)", printer_name, location), + G_VARIANT_TYPE ("(s)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (output == NULL) + { + g_warning ("%s", error->message); + return FALSE; + } + + g_variant_get (output, "(&s)", &ret_error); + if (ret_error[0] != '\0') + { + g_warning ("cups-pk-helper: setting of location for printer %s failed: %s", printer_name, ret_error); + return FALSE; + } + + return TRUE; +} + +gboolean +printer_set_accepting_jobs (const gchar *printer_name, + gboolean accepting_jobs, + const gchar *reason) +{ + g_autoptr(GDBusConnection) bus = NULL; + g_autoptr(GVariant) output = NULL; + const gchar *ret_error; + g_autoptr(GError) error = NULL; + + if (!printer_name) + return TRUE; + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (!bus) + { + g_warning ("Failed to get system bus: %s", error->message); + return TRUE; + } + + output = g_dbus_connection_call_sync (bus, + MECHANISM_BUS, + "/", + MECHANISM_BUS, + "PrinterSetAcceptJobs", + g_variant_new ("(sbs)", + printer_name, + accepting_jobs, + reason ? reason : ""), + G_VARIANT_TYPE ("(s)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (output == NULL) + { + g_warning ("%s", error->message); + return FALSE; + } + + g_variant_get (output, "(&s)", &ret_error); + if (ret_error[0] != '\0') + { + g_warning ("cups-pk-helper: setting of acceptance of jobs for printer %s failed: %s", printer_name, ret_error); + return FALSE; + } + + return TRUE; +} + +gboolean +printer_set_enabled (const gchar *printer_name, + gboolean enabled) +{ + g_autoptr(GDBusConnection) bus = NULL; + g_autoptr(GVariant) output = NULL; + const gchar *ret_error; + g_autoptr(GError) error = NULL; + + if (!printer_name) + return TRUE; + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (!bus) + { + g_warning ("Failed to get system bus: %s", error->message); + return TRUE; + } + + output = g_dbus_connection_call_sync (bus, + MECHANISM_BUS, + "/", + MECHANISM_BUS, + "PrinterSetEnabled", + g_variant_new ("(sb)", printer_name, enabled), + G_VARIANT_TYPE ("(s)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (output == NULL) + { + g_warning ("%s", error->message); + return FALSE; + } + + g_variant_get (output, "(&s)", &ret_error); + if (ret_error[0] != '\0') + { + g_warning ("cups-pk-helper: setting of enablement of printer %s failed: %s", printer_name, ret_error); + return FALSE; + } + + return TRUE; +} + +gboolean +printer_delete (const gchar *printer_name) +{ + g_autoptr(GDBusConnection) bus = NULL; + g_autoptr(GVariant) output = NULL; + const gchar *ret_error; + g_autoptr(GError) error = NULL; + + if (!printer_name) + return TRUE; + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (!bus) + { + g_warning ("Failed to get system bus: %s", error->message); + return TRUE; + } + + output = g_dbus_connection_call_sync (bus, + MECHANISM_BUS, + "/", + MECHANISM_BUS, + "PrinterDelete", + g_variant_new ("(s)", printer_name), + G_VARIANT_TYPE ("(s)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (output == NULL) + { + g_warning ("%s", error->message); + return FALSE; + } + + g_variant_get (output, "(&s)", &ret_error); + if (ret_error[0] != '\0') + { + g_warning ("cups-pk-helper: removing of printer %s failed: %s", printer_name, ret_error); + return FALSE; + } + + return TRUE; +} + +gboolean +printer_set_default (const gchar *printer_name) +{ + const char *cups_server; + g_autoptr(GError) error = NULL; + + if (!printer_name) + return TRUE; + + cups_server = cupsServer (); + if (g_ascii_strncasecmp (cups_server, "localhost", 9) == 0 || + g_ascii_strncasecmp (cups_server, "127.0.0.1", 9) == 0 || + g_ascii_strncasecmp (cups_server, "::1", 3) == 0 || + cups_server[0] == '/') + { + g_autoptr(GDBusConnection) bus = NULL; + g_autoptr(GVariant) output = NULL; + const gchar *ret_error; + + /* Clean .cups/lpoptions before setting + * default printer on local CUPS server. + */ + set_local_default_printer (NULL); + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (!bus) + { + g_warning ("Failed to get system bus: %s", error->message); + return FALSE; + } + + output = g_dbus_connection_call_sync (bus, + MECHANISM_BUS, + "/", + MECHANISM_BUS, + "PrinterSetDefault", + g_variant_new ("(s)", printer_name), + G_VARIANT_TYPE ("(s)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (output == NULL) + { + g_warning ("%s", error->message); + return FALSE; + } + + g_variant_get (output, "(&s)", &ret_error); + if (ret_error[0] != '\0') + { + g_warning ("cups-pk-helper: setting default printer to %s failed: %s", printer_name, ret_error); + return FALSE; + } + + return TRUE; + } + else + /* Store default printer to .cups/lpoptions + * if we are connected to a remote CUPS server. + */ + { + set_local_default_printer (printer_name); + return TRUE; + } +} + +gboolean +printer_set_shared (const gchar *printer_name, + gboolean shared) +{ + g_autoptr(GDBusConnection) bus = NULL; + g_autoptr(GVariant) output = NULL; + const gchar *ret_error; + g_autoptr(GError) error = NULL; + + if (!printer_name) + return TRUE; + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (!bus) + { + g_warning ("Failed to get system bus: %s", error->message); + return TRUE; + } + + output = g_dbus_connection_call_sync (bus, + MECHANISM_BUS, + "/", + MECHANISM_BUS, + "PrinterSetShared", + g_variant_new ("(sb)", printer_name, shared), + G_VARIANT_TYPE ("(s)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (output == NULL) + { + g_warning ("%s", error->message); + return FALSE; + } + + g_variant_get (output, "(&s)", &ret_error); + if (ret_error[0] != '\0') + { + g_warning ("cups-pk-helper: setting of sharing of printer %s failed: %s", printer_name, ret_error); + return FALSE; + } + + return TRUE; +} + +gboolean +printer_set_job_sheets (const gchar *printer_name, + const gchar *start_sheet, + const gchar *end_sheet) +{ + g_autoptr(GDBusConnection) bus = NULL; + g_autoptr(GVariant) output = NULL; + const gchar *ret_error; + g_autoptr(GError) error = NULL; + + if (!printer_name || !start_sheet || !end_sheet) + return TRUE; + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (!bus) + { + g_warning ("Failed to get system bus: %s", error->message); + return TRUE; + } + + output = g_dbus_connection_call_sync (bus, + MECHANISM_BUS, + "/", + MECHANISM_BUS, + "PrinterSetJobSheets", + g_variant_new ("(sss)", printer_name, start_sheet, end_sheet), + G_VARIANT_TYPE ("(s)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (output == NULL) + { + g_warning ("%s", error->message); + return FALSE; + } + + g_variant_get (output, "(&s)", &ret_error); + if (ret_error[0] != '\0') + { + g_warning ("cups-pk-helper: setting of job sheets for printer %s failed: %s", printer_name, ret_error); + return FALSE; + } + + return TRUE; +} + +gboolean +printer_set_policy (const gchar *printer_name, + const gchar *policy, + gboolean error_policy) +{ + g_autoptr(GDBusConnection) bus = NULL; + g_autoptr(GVariant) output = NULL; + const gchar *ret_error; + g_autoptr(GError) error = NULL; + + if (!printer_name || !policy) + return TRUE; + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (!bus) + { + g_warning ("Failed to get system bus: %s", error->message); + return TRUE; + } + + if (error_policy) + output = g_dbus_connection_call_sync (bus, + MECHANISM_BUS, + "/", + MECHANISM_BUS, + "PrinterSetErrorPolicy", + g_variant_new ("(ss)", printer_name, policy), + G_VARIANT_TYPE ("(s)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + else + output = g_dbus_connection_call_sync (bus, + MECHANISM_BUS, + "/", + MECHANISM_BUS, + "PrinterSetOpPolicy", + g_variant_new ("(ss)", printer_name, policy), + G_VARIANT_TYPE ("(s)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (output == NULL) + { + g_warning ("%s", error->message); + return FALSE; + } + + g_variant_get (output, "(&s)", &ret_error); + if (ret_error[0] != '\0') + { + g_warning ("cups-pk-helper: setting of a policy for printer %s failed: %s", printer_name, ret_error); + return FALSE; + } + + return TRUE; +} + +gboolean +printer_set_users (const gchar *printer_name, + gchar **users, + gboolean allowed) +{ + g_autoptr(GDBusConnection) bus = NULL; + GVariantBuilder array_builder; + gint i; + g_autoptr(GVariant) output = NULL; + const gchar *ret_error; + g_autoptr(GError) error = NULL; + + if (!printer_name || !users) + return TRUE; + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (!bus) + { + g_warning ("Failed to get system bus: %s", error->message); + return TRUE; + } + + g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("as")); + for (i = 0; users[i]; i++) + g_variant_builder_add (&array_builder, "s", users[i]); + + if (allowed) + output = g_dbus_connection_call_sync (bus, + MECHANISM_BUS, + "/", + MECHANISM_BUS, + "PrinterSetUsersAllowed", + g_variant_new ("(sas)", printer_name, &array_builder), + G_VARIANT_TYPE ("(s)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + else + output = g_dbus_connection_call_sync (bus, + MECHANISM_BUS, + "/", + MECHANISM_BUS, + "PrinterSetUsersDenied", + g_variant_new ("(sas)", printer_name, &array_builder), + G_VARIANT_TYPE ("(s)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (output == NULL) + { + g_warning ("%s", error->message); + return FALSE; + } + + g_variant_get (output, "(&s)", &ret_error); + if (ret_error[0] != '\0') + { + g_warning ("cups-pk-helper: setting of access list for printer %s failed: %s", printer_name, ret_error); + return FALSE; + } + + return TRUE; +} + +gboolean +class_add_printer (const gchar *class_name, + const gchar *printer_name) +{ + g_autoptr(GDBusConnection) bus = NULL; + g_autoptr(GVariant) output = NULL; + const gchar *ret_error; + g_autoptr(GError) error = NULL; + + if (!class_name || !printer_name) + return TRUE; + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (!bus) + { + g_warning ("Failed to get system bus: %s", error->message); + return TRUE; + } + + output = g_dbus_connection_call_sync (bus, + MECHANISM_BUS, + "/", + MECHANISM_BUS, + "ClassAddPrinter", + g_variant_new ("(ss)", class_name, printer_name), + G_VARIANT_TYPE ("(s)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + + if (output == NULL) + { + g_warning ("%s", error->message); + return FALSE; + } + + g_variant_get (output, "(&s)", &ret_error); + if (ret_error[0] != '\0') + { + g_warning ("cups-pk-helper: adding of printer %s to class %s failed: %s", printer_name, class_name, ret_error); + return FALSE; + } + + return TRUE; +} + +gboolean +printer_is_local (cups_ptype_t printer_type, + const gchar *device_uri) +{ + gboolean result = TRUE; + char scheme[HTTP_MAX_URI]; + char username[HTTP_MAX_URI]; + char hostname[HTTP_MAX_URI]; + char resource[HTTP_MAX_URI]; + int port; + + if (printer_type & + (CUPS_PRINTER_DISCOVERED | + CUPS_PRINTER_REMOTE | + CUPS_PRINTER_IMPLICIT)) + result = FALSE; + + if (device_uri == NULL || !result) + return result; + + httpSeparateURI (HTTP_URI_CODING_ALL, device_uri, + scheme, sizeof (scheme), + username, sizeof (username), + hostname, sizeof (hostname), + &port, + resource, sizeof (resource)); + + if (g_str_equal (scheme, "ipp") || + g_str_equal (scheme, "smb") || + g_str_equal (scheme, "socket") || + g_str_equal (scheme, "lpd")) + result = FALSE; + + return result; +} + +gchar* +printer_get_hostname (cups_ptype_t printer_type, + const gchar *device_uri, + const gchar *printer_uri) +{ + gboolean local = TRUE; + gchar *result = NULL; + char scheme[HTTP_MAX_URI]; + char username[HTTP_MAX_URI]; + char hostname[HTTP_MAX_URI]; + char resource[HTTP_MAX_URI]; + int port; + + if (device_uri == NULL) + return result; + + if (printer_type & (CUPS_PRINTER_DISCOVERED | + CUPS_PRINTER_REMOTE | + CUPS_PRINTER_IMPLICIT)) + { + if (printer_uri) + { + httpSeparateURI (HTTP_URI_CODING_ALL, printer_uri, + scheme, sizeof (scheme), + username, sizeof (username), + hostname, sizeof (hostname), + &port, + resource, sizeof (resource)); + + if (hostname[0] != '\0') + result = g_strdup (hostname); + } + + local = FALSE; + } + + if (result == NULL && device_uri) + { + httpSeparateURI (HTTP_URI_CODING_ALL, device_uri, + scheme, sizeof (scheme), + username, sizeof (username), + hostname, sizeof (hostname), + &port, + resource, sizeof (resource)); + + if (g_str_equal (scheme, "ipp") || + g_str_equal (scheme, "smb") || + g_str_equal (scheme, "socket") || + g_str_equal (scheme, "lpd")) + { + if (hostname[0] != '\0') + result = g_strdup (hostname); + + local = FALSE; + } + } + + if (local) + result = g_strdup ("localhost"); + + return result; +} + +/* Returns default page size for current locale */ +const gchar * +get_page_size_from_locale (void) +{ + if (g_str_equal (gtk_paper_size_get_default (), GTK_PAPER_NAME_LETTER)) + return "Letter"; + else + return "A4"; +} + +typedef struct +{ + gchar *printer_name; + gchar **attributes_names; + GHashTable *result; + GIACallback callback; + gpointer user_data; + GMainContext *context; +} GIAData; + +static gboolean +get_ipp_attributes_idle_cb (gpointer user_data) +{ + GIAData *data = (GIAData *) user_data; + + data->callback (data->result, data->user_data); + + return FALSE; +} + +static void +get_ipp_attributes_data_free (gpointer user_data) +{ + GIAData *data = (GIAData *) user_data; + + if (data->context) + g_main_context_unref (data->context); + g_free (data->printer_name); + if (data->attributes_names) + g_strfreev (data->attributes_names); + g_free (data); +} + +static void +get_ipp_attributes_cb (gpointer user_data) +{ + GIAData *data = (GIAData *) user_data; + GSource *idle_source; + + idle_source = g_idle_source_new (); + g_source_set_callback (idle_source, + get_ipp_attributes_idle_cb, + data, + get_ipp_attributes_data_free); + g_source_attach (idle_source, data->context); + g_source_unref (idle_source); +} + +static void +ipp_attribute_free2 (gpointer attr) +{ + IPPAttribute *attribute = (IPPAttribute *) attr; + ipp_attribute_free (attribute); +} + +static gpointer +get_ipp_attributes_func (gpointer user_data) +{ + ipp_attribute_t *attr = NULL; + GIAData *data = (GIAData *) user_data; + ipp_t *request; + ipp_t *response = NULL; + g_autofree gchar *printer_uri = NULL; + char **requested_attrs = NULL; + gint i, j, length = 0; + + printer_uri = g_strdup_printf ("ipp://localhost/printers/%s", data->printer_name); + + if (data->attributes_names) + { + length = g_strv_length (data->attributes_names); + + requested_attrs = g_new0 (char *, length); + for (i = 0; data->attributes_names[i]; i++) + requested_attrs[i] = g_strdup (data->attributes_names[i]); + + request = ippNewRequest (IPP_GET_PRINTER_ATTRIBUTES); + ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, + "printer-uri", NULL, printer_uri); + ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, + "requested-attributes", length, NULL, (const char **) requested_attrs); + response = cupsDoRequest (CUPS_HTTP_DEFAULT, request, "/"); + } + + if (response) + { + if (ippGetStatusCode (response) <= IPP_OK_CONFLICT) + { + for (j = 0; j < length; j++) + { + attr = ippFindAttribute (response, requested_attrs[j], IPP_TAG_ZERO); + if (attr && ippGetCount (attr) > 0 && ippGetValueTag (attr) != IPP_TAG_NOVALUE) + { + IPPAttribute *attribute; + + attribute = g_new0 (IPPAttribute, 1); + attribute->attribute_name = g_strdup (requested_attrs[j]); + attribute->attribute_values = g_new0 (IPPAttributeValue, ippGetCount (attr)); + attribute->num_of_values = ippGetCount (attr); + + if (ippGetValueTag (attr) == IPP_TAG_INTEGER || + ippGetValueTag (attr) == IPP_TAG_ENUM) + { + attribute->attribute_type = IPP_ATTRIBUTE_TYPE_INTEGER; + + for (i = 0; i < ippGetCount (attr); i++) + attribute->attribute_values[i].integer_value = ippGetInteger (attr, i); + } + else if (ippGetValueTag (attr) == IPP_TAG_NAME || + ippGetValueTag (attr) == IPP_TAG_STRING || + ippGetValueTag (attr) == IPP_TAG_TEXT || + ippGetValueTag (attr) == IPP_TAG_URI || + ippGetValueTag (attr) == IPP_TAG_KEYWORD || + ippGetValueTag (attr) == IPP_TAG_URISCHEME) + { + attribute->attribute_type = IPP_ATTRIBUTE_TYPE_STRING; + + for (i = 0; i < ippGetCount (attr); i++) + attribute->attribute_values[i].string_value = g_strdup (ippGetString (attr, i, NULL)); + } + else if (ippGetValueTag (attr) == IPP_TAG_RANGE) + { + attribute->attribute_type = IPP_ATTRIBUTE_TYPE_RANGE; + + for (i = 0; i < ippGetCount (attr); i++) + { + attribute->attribute_values[i].lower_range = + ippGetRange (attr, i, &(attribute->attribute_values[i].upper_range)); + } + } + else if (ippGetValueTag (attr) == IPP_TAG_BOOLEAN) + { + attribute->attribute_type = IPP_ATTRIBUTE_TYPE_BOOLEAN; + + for (i = 0; i < ippGetCount (attr); i++) + attribute->attribute_values[i].boolean_value = ippGetBoolean (attr, i); + } + + if (!data->result) + data->result = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, ipp_attribute_free2); + + g_hash_table_insert (data->result, g_strdup (requested_attrs[j]), attribute); + } + } + } + + ippDelete (response); + } + + + for (i = 0; i < length; i++) + g_free (requested_attrs[i]); + g_free (requested_attrs); + + get_ipp_attributes_cb (data); + + return NULL; +} + +void +get_ipp_attributes_async (const gchar *printer_name, + gchar **attributes_names, + GIACallback callback, + gpointer user_data) +{ + GIAData *data; + GThread *thread; + g_autoptr(GError) error = NULL; + + data = g_new0 (GIAData, 1); + data->printer_name = g_strdup (printer_name); + data->attributes_names = g_strdupv (attributes_names); + data->callback = callback; + data->user_data = user_data; + data->context = g_main_context_ref_thread_default (); + + thread = g_thread_try_new ("get-ipp-attributes", + get_ipp_attributes_func, + data, + &error); + + if (!thread) + { + g_warning ("%s", error->message); + callback (NULL, user_data); + + get_ipp_attributes_data_free (data); + } + else + { + g_thread_unref (thread); + } +} + +IPPAttribute * +ipp_attribute_copy (IPPAttribute *attr) +{ + IPPAttribute *result = NULL; + gint i; + + if (attr) + { + result = g_new0 (IPPAttribute, 1); + + *result = *attr; + result->attribute_name = g_strdup (attr->attribute_name); + result->attribute_values = g_new0 (IPPAttributeValue, attr->num_of_values); + for (i = 0; i < attr->num_of_values; i++) + { + result->attribute_values[i] = attr->attribute_values[i]; + if (attr->attribute_values[i].string_value) + result->attribute_values[i].string_value = g_strdup (attr->attribute_values[i].string_value); + } + } + + return result; +} + +void +ipp_attribute_free (IPPAttribute *attr) +{ + gint i; + + if (attr) + { + for (i = 0; i < attr->num_of_values; i++) + g_free (attr->attribute_values[i].string_value); + + g_free (attr->attribute_values); + g_free (attr->attribute_name); + g_free (attr); + } +} + + + +typedef struct +{ + gchar *printer_name; + gchar *ppd_copy; + GCancellable *cancellable; + PSPCallback callback; + gpointer user_data; +} PSPData; + +static void +printer_set_ppd_async_dbus_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GVariant) output = NULL; + gboolean result = FALSE; + PSPData *data = (PSPData *) user_data; + g_autoptr(GError) error = NULL; + + output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), + res, + &error); + g_object_unref (source_object); + + if (output) + { + const gchar *ret_error; + + g_variant_get (output, "(&s)", &ret_error); + if (ret_error[0] != '\0') + g_warning ("cups-pk-helper: setting of driver for printer %s failed: %s", data->printer_name, ret_error); + else + result = TRUE; + } + else + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("%s", error->message); + } + + /* Don't call callback if cancelled */ + if (!data->cancellable || + !g_cancellable_is_cancelled (data->cancellable)) + data->callback (data->printer_name, + result, + data->user_data); + + if (data->cancellable) + g_object_unref (data->cancellable); + + if (data->ppd_copy) + { + g_unlink (data->ppd_copy); + g_free (data->ppd_copy); + } + + g_free (data->printer_name); + g_free (data); +} + +/* + * Set ppd for given printer. + * Don't use this for classes, just for printers. + */ +void +printer_set_ppd_async (const gchar *printer_name, + const gchar *ppd_name, + GCancellable *cancellable, + PSPCallback callback, + gpointer user_data) +{ + GDBusConnection *bus; + PSPData *data; + g_autoptr(GError) error = NULL; + + data = g_new0 (PSPData, 1); + if (cancellable) + data->cancellable = g_object_ref (cancellable); + data->callback = callback; + data->user_data = user_data; + data->printer_name = g_strdup (printer_name); + + if (printer_name == NULL || + printer_name[0] == '\0') + { + goto out; + } + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (!bus) + { + g_warning ("Failed to get system bus: %s", error->message); + goto out; + } + + g_dbus_connection_call (bus, + MECHANISM_BUS, + "/", + MECHANISM_BUS, + "PrinterAdd", + g_variant_new ("(sssss)", + printer_name, + "", + ppd_name, + "", + ""), + G_VARIANT_TYPE ("(s)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + data->cancellable, + printer_set_ppd_async_dbus_cb, + data); + + return; + +out: + callback (printer_name, FALSE, user_data); + + if (data->cancellable) + g_object_unref (data->cancellable); + g_free (data->printer_name); + g_free (data); +} + +static void +printer_set_ppd_file_async_scb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GDBusConnection *bus; + gboolean success; + PSPData *data = (PSPData *) user_data; + g_autoptr(GError) error = NULL; + + success = g_file_copy_finish (G_FILE (source_object), + res, + &error); + g_object_unref (source_object); + + if (!success) + { + g_warning ("%s", error->message); + goto out; + } + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (!bus) + { + g_warning ("Failed to get system bus: %s", error->message); + goto out; + } + + g_dbus_connection_call (bus, + MECHANISM_BUS, + "/", + MECHANISM_BUS, + "PrinterAddWithPpdFile", + g_variant_new ("(sssss)", + data->printer_name, + "", + data->ppd_copy, + "", + ""), + G_VARIANT_TYPE ("(s)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + data->cancellable, + printer_set_ppd_async_dbus_cb, + data); + + return; + +out: + data->callback (data->printer_name, FALSE, data->user_data); + + if (data->cancellable) + g_object_unref (data->cancellable); + g_free (data->printer_name); + g_free (data->ppd_copy); + g_free (data); +} + +/* + * Set ppd for given printer. + * Don't use this for classes, just for printers. + */ +void +printer_set_ppd_file_async (const gchar *printer_name, + const gchar *ppd_filename, + GCancellable *cancellable, + PSPCallback callback, + gpointer user_data) +{ + GFileIOStream *stream; + PSPData *data; + GFile *source_ppd_file; + GFile *destination_ppd_file; + + data = g_new0 (PSPData, 1); + if (cancellable) + data->cancellable = g_object_ref (cancellable); + data->callback = callback; + data->user_data = user_data; + data->printer_name = g_strdup (printer_name); + + if (printer_name == NULL || + printer_name[0] == '\0') + { + goto out; + } + + /* + * We need to copy the PPD to temp directory at first. + * This is needed because of SELinux. + */ + source_ppd_file = g_file_new_for_path (ppd_filename); + destination_ppd_file = g_file_new_tmp ("g-c-c-XXXXXX.ppd", &stream, NULL); + g_object_unref (stream); + data->ppd_copy = g_strdup (g_file_get_path (destination_ppd_file)); + + g_file_copy_async (source_ppd_file, + destination_ppd_file, + G_FILE_COPY_OVERWRITE, + G_PRIORITY_DEFAULT, + cancellable, + NULL, + NULL, + printer_set_ppd_file_async_scb, + data); + + g_object_unref (destination_ppd_file); + + return; + +out: + callback (printer_name, FALSE, user_data); + + if (data->cancellable) + g_object_unref (data->cancellable); + g_free (data->printer_name); + g_free (data); +} + + + +typedef void (*GPACallback) (gchar **attribute_values, + gpointer user_data); + +typedef struct +{ + gchar *attribute_name; + gchar **ppds_names; + gchar **result; + GPACallback callback; + gpointer user_data; + GMainContext *context; +} GPAData; + +static gboolean +get_ppds_attribute_idle_cb (gpointer user_data) +{ + GPAData *data = (GPAData *) user_data; + + data->callback (data->result, data->user_data); + + return FALSE; +} + +static void +get_ppds_attribute_data_free (gpointer user_data) +{ + GPAData *data = (GPAData *) user_data; + + if (data->context) + g_main_context_unref (data->context); + g_free (data->attribute_name); + g_strfreev (data->ppds_names); + g_free (data); +} + +static void +get_ppds_attribute_cb (gpointer user_data) +{ + GPAData *data = (GPAData *) user_data; + GSource *idle_source; + + idle_source = g_idle_source_new (); + g_source_set_callback (idle_source, + get_ppds_attribute_idle_cb, + data, + get_ppds_attribute_data_free); + g_source_attach (idle_source, data->context); + g_source_unref (idle_source); +} + +static gpointer +get_ppds_attribute_func (gpointer user_data) +{ + ppd_file_t *ppd_file; + ppd_attr_t *ppd_attr; + GPAData *data = (GPAData *) user_data; + gint i; + + data->result = g_new0 (gchar *, g_strv_length (data->ppds_names) + 1); + for (i = 0; data->ppds_names[i]; i++) + { + g_autofree gchar *ppd_filename = g_strdup (cupsGetServerPPD (CUPS_HTTP_DEFAULT, data->ppds_names[i])); + if (ppd_filename) + { + ppd_file = ppdOpenFile (ppd_filename); + if (ppd_file) + { + ppd_attr = ppdFindAttr (ppd_file, data->attribute_name, NULL); + if (ppd_attr != NULL) + data->result[i] = g_strdup (ppd_attr->value); + + ppdClose (ppd_file); + } + + g_unlink (ppd_filename); + } + } + + get_ppds_attribute_cb (data); + + return NULL; +} + +/* + * Get values of requested PPD attribute for given PPDs. + */ +static void +get_ppds_attribute_async (gchar **ppds_names, + gchar *attribute_name, + GPACallback callback, + gpointer user_data) +{ + GPAData *data; + GThread *thread; + g_autoptr(GError) error = NULL; + + if (!ppds_names || !attribute_name) + { + callback (NULL, user_data); + return; + } + + data = g_new0 (GPAData, 1); + data->ppds_names = g_strdupv (ppds_names); + data->attribute_name = g_strdup (attribute_name); + data->callback = callback; + data->user_data = user_data; + data->context = g_main_context_ref_thread_default (); + + thread = g_thread_try_new ("get-ppds-attribute", + get_ppds_attribute_func, + data, + &error); + + if (!thread) + { + g_warning ("%s", error->message); + callback (NULL, user_data); + + get_ppds_attribute_data_free (data); + } + else + { + g_thread_unref (thread); + } +} + + + +typedef void (*GDACallback) (gchar *device_id, + gchar *device_make_and_model, + gchar *device_uri, + gpointer user_data); + +typedef struct +{ + gchar *printer_name; + gchar *device_uri; + GCancellable *cancellable; + GList *backend_list; + GDACallback callback; + gpointer user_data; +} GDAData; + +typedef struct +{ + gchar *printer_name; + gint count; + PPDName **result; + GCancellable *cancellable; + GPNCallback callback; + gpointer user_data; +} GPNData; + +static void +get_ppd_names_async_cb (gchar **attribute_values, + gpointer user_data) +{ + GPNData *data = (GPNData *) user_data; + gint i; + + if (g_cancellable_is_cancelled (data->cancellable)) + { + g_strfreev (attribute_values); + + for (i = 0; data->result[i]; i++) + { + g_free (data->result[i]->ppd_name); + g_free (data->result[i]); + } + + g_free (data->result); + data->result = NULL; + + goto out; + } + + if (attribute_values) + { + for (i = 0; attribute_values[i]; i++) + data->result[i]->ppd_display_name = attribute_values[i]; + + g_free (attribute_values); + } + +out: + data->callback (data->result, + data->printer_name, + g_cancellable_is_cancelled (data->cancellable), + data->user_data); + + if (data->cancellable) + g_object_unref (data->cancellable); + g_free (data->printer_name); + g_free (data); +} + +static void +get_ppd_names_async_dbus_scb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GVariant) output = NULL; + PPDName *ppd_item; + PPDName **result = NULL; + GPNData *data = (GPNData *) user_data; + g_autoptr(GError) error = NULL; + GList *driver_list = NULL; + GList *iter; + gint i, j, n = 0; + static const char * const match_levels[] = { + "exact-cmd", + "exact", + "close", + "generic", + "none"}; + + output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), + res, + &error); + g_object_unref (source_object); + + if (output) + { + g_autoptr(GVariant) array = NULL; + + g_variant_get (output, "(@a(ss))", + &array); + + for (j = 0; j < G_N_ELEMENTS (match_levels) && n < data->count; j++) + { + g_autoptr(GVariantIter) iter = NULL; + const gchar *driver, *match; + + g_variant_get (array, + "a(ss)", + &iter); + + while (g_variant_iter_next (iter, "(&s&s)", &driver, &match)) + { + if (g_str_equal (match, match_levels[j]) && n < data->count) + { + ppd_item = g_new0 (PPDName, 1); + ppd_item->ppd_name = g_strdup (driver); + + if (g_strcmp0 (match, "exact-cmd") == 0) + ppd_item->ppd_match_level = PPD_EXACT_CMD_MATCH; + else if (g_strcmp0 (match, "exact") == 0) + ppd_item->ppd_match_level = PPD_EXACT_MATCH; + else if (g_strcmp0 (match, "close") == 0) + ppd_item->ppd_match_level = PPD_CLOSE_MATCH; + else if (g_strcmp0 (match, "generic") == 0) + ppd_item->ppd_match_level = PPD_GENERIC_MATCH; + else if (g_strcmp0 (match, "none") == 0) + ppd_item->ppd_match_level = PPD_NO_MATCH; + + driver_list = g_list_append (driver_list, ppd_item); + + n++; + } + } + } + } + else + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("%s", error->message); + } + + if (n > 0) + { + result = g_new0 (PPDName *, n + 1); + i = 0; + for (iter = driver_list; iter; iter = iter->next) + { + result[i] = iter->data; + i++; + } + } + + if (result) + { + gchar **ppds_names; + + data->result = result; + + ppds_names = g_new0 (gchar *, n + 1); + for (i = 0; i < n; i++) + ppds_names[i] = g_strdup (result[i]->ppd_name); + + get_ppds_attribute_async (ppds_names, + "NickName", + get_ppd_names_async_cb, + data); + + g_strfreev (ppds_names); + } + else + { + data->callback (NULL, + data->printer_name, + g_cancellable_is_cancelled (data->cancellable), + data->user_data); + + if (data->cancellable) + g_object_unref (data->cancellable); + g_free (data->printer_name); + g_free (data); + } +} + +static void +get_device_attributes_cb (gchar *device_id, + gchar *device_make_and_model, + gchar *device_uri, + gpointer user_data) +{ + GDBusConnection *bus; + g_autoptr(GError) error = NULL; + GPNData *data = (GPNData *) user_data; + + if (g_cancellable_is_cancelled (data->cancellable)) + goto out; + + if (!device_id || !device_make_and_model || !device_uri) + goto out; + + bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + if (!bus) + { + g_warning ("Failed to get system bus: %s", error->message); + goto out; + } + + g_dbus_connection_call (bus, + SCP_BUS, + SCP_PATH, + SCP_IFACE, + "GetBestDrivers", + g_variant_new ("(sss)", + device_id, + device_make_and_model, + device_uri), + G_VARIANT_TYPE ("(a(ss))"), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT_LONG, + data->cancellable, + get_ppd_names_async_dbus_scb, + data); + + return; + +out: + data->callback (NULL, + data->printer_name, + g_cancellable_is_cancelled (data->cancellable), + data->user_data); + + if (data->cancellable) + g_object_unref (data->cancellable); + g_free (data->printer_name); + g_free (data); +} + +/* + * Special item for the list of backends. It represents + * backends not present in the list itself. + */ +#define OTHER_BACKENDS "other-backends" + +/* + * List of CUPS backends sorted according to their speed, + * the fastest is the first one. The last item represents + * backends not present in the list. + */ +const gchar *cups_backends[] = { + "usb", + "socket", + "serial", + "parallel", + "lpd", + "ipp", + "hp", + "dnssd", + "snmp", + "bluetooth", + "beh", + "ncp", + "hpfax", + OTHER_BACKENDS +}; + +static GList * +create_backends_list () +{ + GList *list = NULL; + gint i; + + for (i = 0; i < G_N_ELEMENTS (cups_backends); i++) + list = g_list_prepend (list, g_strdup (cups_backends[i])); + list = g_list_reverse (list); + + return list; +} + +static GVariantBuilder * +create_other_backends_array () +{ + GVariantBuilder *builder; + gint i; + + builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); + for (i = 0; i < G_N_ELEMENTS (cups_backends) - 1; i++) + g_variant_builder_add (builder, "s", cups_backends[i]); + + return builder; +} + +static void +get_device_attributes_async_dbus_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) + +{ + g_autoptr(GVariant) output = NULL; + GDAData *data = (GDAData *) user_data; + g_autoptr(GError) error = NULL; + GList *tmp; + gchar *device_id = NULL; + gchar *device_make_and_model = NULL; + + output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), + res, + &error); + g_object_unref (source_object); + + if (output) + { + const gchar *ret_error; + g_autoptr(GVariant) devices_variant = NULL; + gint index = -1; + + g_variant_get (output, "(&s@a{ss})", + &ret_error, + &devices_variant); + + if (ret_error[0] != '\0') + { + g_warning ("cups-pk-helper: getting of attributes for printer %s failed: %s", data->printer_name, ret_error); + } + + if (data->device_uri) + { + g_autoptr(GVariantIter) iter = NULL; + const gchar *key, *value; + g_autofree gchar *suffix = NULL; + + g_variant_get (devices_variant, + "a{ss}", + &iter); + + while (g_variant_iter_next (iter, "{&s&s}", &key, &value)) + { + if (g_str_equal (value, data->device_uri)) + { + gchar *number = g_strrstr (key, ":"); + if (number != NULL) + { + gchar *endptr; + + number++; + index = g_ascii_strtoll (number, &endptr, 10); + if (index == 0 && endptr == (number)) + index = -1; + } + } + } + + suffix = g_strdup_printf (":%d", index); + + g_variant_get (devices_variant, + "a{ss}", + &iter); + + while (g_variant_iter_next (iter, "{&s&s}", &key, &value)) + { + if (g_str_has_suffix (key, suffix)) + { + if (g_str_has_prefix (key, "device-id")) + { + device_id = g_strdup (value); + } + + if (g_str_has_prefix (key, "device-make-and-model")) + { + device_make_and_model = g_strdup (value); + } + } + } + } + } + else + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("%s", error->message); + } + + if (!device_id || !device_make_and_model) + { + GVariantBuilder *include_scheme_builder = NULL; + GVariantBuilder *exclude_scheme_builder = NULL; + + g_free (device_id); + g_free (device_make_and_model); + + device_id = NULL; + device_make_and_model = NULL; + + if (data->backend_list && !g_cancellable_is_cancelled (data->cancellable)) + { + const gchar *backend_name; + + backend_name = data->backend_list->data; + + if (g_strcmp0 (backend_name, OTHER_BACKENDS) != 0) + { + include_scheme_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); + g_variant_builder_add (include_scheme_builder, "s", backend_name); + } + else + { + exclude_scheme_builder = create_other_backends_array (); + } + + tmp = data->backend_list; + data->backend_list = g_list_remove_link (data->backend_list, tmp); + g_list_free_full (tmp, g_free); + + g_dbus_connection_call (G_DBUS_CONNECTION (g_object_ref (source_object)), + MECHANISM_BUS, + "/", + MECHANISM_BUS, + "DevicesGet", + g_variant_new ("(iiasas)", + 0, + 0, + include_scheme_builder, + exclude_scheme_builder), + G_VARIANT_TYPE ("(sa{ss})"), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT, + data->cancellable, + get_device_attributes_async_dbus_cb, + user_data); + + if (include_scheme_builder) + g_variant_builder_unref (include_scheme_builder); + + if (exclude_scheme_builder) + g_variant_builder_unref (exclude_scheme_builder); + + return; + } + } + + g_object_unref (source_object); + + if (data->backend_list) + { + g_list_free_full (data->backend_list, g_free); + data->backend_list = NULL; + } + + data->callback (device_id, + device_make_and_model, + data->device_uri, + data->user_data); + + if (data->cancellable) + g_object_unref (data->cancellable); + g_free (data->device_uri); + g_free (data->printer_name); + g_free (data); +} + +static void +get_device_attributes_async_scb (GHashTable *result, + gpointer user_data) +{ + GDBusConnection *bus; + GVariantBuilder include_scheme_builder; + IPPAttribute *attr; + GDAData *data = (GDAData *) user_data; + g_autoptr(GError) error = NULL; + GList *tmp; + + if (result) + { + attr = g_hash_table_lookup (result, "device-uri"); + if (attr && attr->attribute_type == IPP_ATTRIBUTE_TYPE_STRING && + attr->num_of_values > 0) + data->device_uri = g_strdup (attr->attribute_values[0].string_value); + g_hash_table_unref (result); + } + + if (g_cancellable_is_cancelled (data->cancellable)) + goto out; + + if (!data->device_uri) + goto out; + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (!bus) + { + g_warning ("Failed to get system bus: %s", error->message); + goto out; + } + + data->backend_list = create_backends_list (); + + g_variant_builder_init (&include_scheme_builder, G_VARIANT_TYPE ("as")); + g_variant_builder_add (&include_scheme_builder, "s", data->backend_list->data); + + tmp = data->backend_list; + data->backend_list = g_list_remove_link (data->backend_list, tmp); + g_list_free_full (tmp, g_free); + + g_dbus_connection_call (g_object_ref (bus), + MECHANISM_BUS, + "/", + MECHANISM_BUS, + "DevicesGet", + g_variant_new ("(iiasas)", + 0, + 0, + &include_scheme_builder, + NULL), + G_VARIANT_TYPE ("(sa{ss})"), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT, + data->cancellable, + get_device_attributes_async_dbus_cb, + data); + + return; + +out: + data->callback (NULL, NULL, NULL, data->user_data); + + if (data->cancellable) + g_object_unref (data->cancellable); + g_free (data->device_uri); + g_free (data->printer_name); + g_free (data); +} + +/* + * Get device-id, device-make-and-model and device-uri for given printer. + */ +static void +get_device_attributes_async (const gchar *printer_name, + GCancellable *cancellable, + GDACallback callback, + gpointer user_data) +{ + GDAData *data; + gchar **attributes; + + if (!printer_name) + { + callback (NULL, NULL, NULL, user_data); + return; + } + + data = g_new0 (GDAData, 1); + data->printer_name = g_strdup (printer_name); + if (cancellable) + data->cancellable = g_object_ref (cancellable); + data->callback = callback; + data->user_data = user_data; + + attributes = g_new0 (gchar *, 2); + attributes[0] = g_strdup ("device-uri"); + + get_ipp_attributes_async (printer_name, + attributes, + get_device_attributes_async_scb, + data); + + g_strfreev (attributes); +} + +/* + * Return "count" best matching driver names for given printer. + */ +void +get_ppd_names_async (gchar *printer_name, + gint count, + GCancellable *cancellable, + GPNCallback callback, + gpointer user_data) +{ + GPNData *data; + + if (!printer_name) + { + callback (NULL, NULL, TRUE, user_data); + return; + } + + data = g_new0 (GPNData, 1); + data->printer_name = g_strdup (printer_name); + data->count = count; + if (cancellable) + data->cancellable = g_object_ref (cancellable); + data->callback = callback; + data->user_data = user_data; + + /* + * We have to find out device-id for this printer at first. + */ + get_device_attributes_async (printer_name, + cancellable, + get_device_attributes_cb, + data); +} + +typedef struct +{ + PPDList *result; + GCancellable *cancellable; + GAPCallback callback; + gpointer user_data; + GMainContext *context; +} GAPData; + +static gboolean +get_all_ppds_idle_cb (gpointer user_data) +{ + GAPData *data = (GAPData *) user_data; + + /* Don't call callback if cancelled */ + if (data->cancellable && + g_cancellable_is_cancelled (data->cancellable)) + { + ppd_list_free (data->result); + data->result = NULL; + } + else + { + data->callback (data->result, data->user_data); + } + + return FALSE; +} + +static void +get_all_ppds_data_free (gpointer user_data) +{ + GAPData *data = (GAPData *) user_data; + + if (data->context) + g_main_context_unref (data->context); + if (data->cancellable) + g_object_unref (data->cancellable); + g_free (data); +} + +static void +get_all_ppds_cb (gpointer user_data) +{ + GAPData *data = (GAPData *) user_data; + GSource *idle_source; + + idle_source = g_idle_source_new (); + g_source_set_callback (idle_source, + get_all_ppds_idle_cb, + data, + get_all_ppds_data_free); + g_source_attach (idle_source, data->context); + g_source_unref (idle_source); +} + +static const struct { + const char *normalized_name; + const char *display_name; +} manufacturers_names[] = { + { "alps", "Alps" }, + { "anitech", "Anitech" }, + { "apple", "Apple" }, + { "apollo", "Apollo" }, + { "brother", "Brother" }, + { "canon", "Canon" }, + { "citizen", "Citizen" }, + { "citoh", "Citoh" }, + { "compaq", "Compaq" }, + { "dec", "DEC" }, + { "dell", "Dell" }, + { "dnp", "DNP" }, + { "dymo", "Dymo" }, + { "epson", "Epson" }, + { "fujifilm", "Fujifilm" }, + { "fujitsu", "Fujitsu" }, + { "gelsprinter", "Ricoh" }, + { "generic", "Generic" }, + { "genicom", "Genicom" }, + { "gestetner", "Gestetner" }, + { "hewlett packard", "Hewlett-Packard" }, + { "heidelberg", "Heidelberg" }, + { "hitachi", "Hitachi" }, + { "hp", "Hewlett-Packard" }, + { "ibm", "IBM" }, + { "imagen", "Imagen" }, + { "imagistics", "Imagistics" }, + { "infoprint", "InfoPrint" }, + { "infotec", "Infotec" }, + { "intellitech", "Intellitech" }, + { "kodak", "Kodak" }, + { "konica minolta", "Minolta" }, + { "kyocera", "Kyocera" }, + { "kyocera mita", "Kyocera" }, + { "lanier", "Lanier" }, + { "lexmark international", "Lexmark" }, + { "lexmark", "Lexmark" }, + { "minolta", "Minolta" }, + { "minolta qms", "Minolta" }, + { "mitsubishi", "Mitsubishi" }, + { "nec", "NEC" }, + { "nrg", "NRG" }, + { "oce", "Oce" }, + { "oki", "Oki" }, + { "oki data corp", "Oki" }, + { "olivetti", "Olivetti" }, + { "olympus", "Olympus" }, + { "panasonic", "Panasonic" }, + { "pcpi", "PCPI" }, + { "pentax", "Pentax" }, + { "qms", "QMS" }, + { "raven", "Raven" }, + { "raw", "Raw" }, + { "ricoh", "Ricoh" }, + { "samsung", "Samsung" }, + { "savin", "Savin" }, + { "seiko", "Seiko" }, + { "sharp", "Sharp" }, + { "shinko", "Shinko" }, + { "sipix", "SiPix" }, + { "sony", "Sony" }, + { "star", "Star" }, + { "tally", "Tally" }, + { "tektronix", "Tektronix" }, + { "texas instruments", "Texas Instruments" }, + { "toshiba", "Toshiba" }, + { "toshiba tec corp.", "Toshiba" }, + { "xante", "Xante" }, + { "xerox", "Xerox" }, + { "zebra", "Zebra" }, +}; + +static gpointer +get_all_ppds_func (gpointer user_data) +{ + ipp_attribute_t *attr; + GHashTable *ppds_hash = NULL; + GHashTable *manufacturers_hash = NULL; + GAPData *data = (GAPData *) user_data; + PPDName *item; + ipp_t *request; + ipp_t *response; + GList *list; + gchar *manufacturer_display_name; + gint i, j; + + request = ippNewRequest (CUPS_GET_PPDS); + response = cupsDoRequest (CUPS_HTTP_DEFAULT, request, "/"); + + if (response && + ippGetStatusCode (response) <= IPP_OK_CONFLICT) + { + /* + * This hash contains names of manufacturers as keys and + * values are GLists of PPD names. + */ + ppds_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + /* + * This hash contains all possible names of manufacturers as keys + * and values are just first occurrences of their equivalents. + * This is for mapping of e.g. "Hewlett Packard" and "HP" to the same name + * (the one which comes first). + */ + manufacturers_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + for (i = 0; i < G_N_ELEMENTS (manufacturers_names); i++) + { + g_hash_table_insert (manufacturers_hash, + g_strdup (manufacturers_names[i].normalized_name), + g_strdup (manufacturers_names[i].display_name)); + } + + for (attr = ippFirstAttribute (response); attr != NULL; attr = ippNextAttribute (response)) + { + const gchar *ppd_device_id = NULL; + const gchar *ppd_make_and_model = NULL; + const gchar *ppd_name = NULL; + const gchar *ppd_product = NULL; + const gchar *ppd_make = NULL; + g_autofree gchar *mdl = NULL; + g_autofree gchar *mfg = NULL; + g_autofree gchar *mfg_normalized = NULL; + + while (attr != NULL && ippGetGroupTag (attr) != IPP_TAG_PRINTER) + attr = ippNextAttribute (response); + + if (attr == NULL) + break; + + while (attr != NULL && ippGetGroupTag (attr) == IPP_TAG_PRINTER) + { + if (g_strcmp0 (ippGetName (attr), "ppd-device-id") == 0 && + ippGetValueTag (attr) == IPP_TAG_TEXT) + ppd_device_id = ippGetString (attr, 0, NULL); + else if (g_strcmp0 (ippGetName (attr), "ppd-make-and-model") == 0 && + ippGetValueTag (attr) == IPP_TAG_TEXT) + ppd_make_and_model = ippGetString (attr, 0, NULL); + else if (g_strcmp0 (ippGetName (attr), "ppd-name") == 0 && + ippGetValueTag (attr) == IPP_TAG_NAME) + ppd_name = ippGetString (attr, 0, NULL); + else if (g_strcmp0 (ippGetName (attr), "ppd-product") == 0 && + ippGetValueTag (attr) == IPP_TAG_TEXT) + ppd_product = ippGetString (attr, 0, NULL); + else if (g_strcmp0 (ippGetName (attr), "ppd-make") == 0 && + ippGetValueTag (attr) == IPP_TAG_TEXT) + ppd_make = ippGetString (attr, 0, NULL); + + attr = ippNextAttribute (response); + } + + /* Get manufacturer's name */ + if (ppd_device_id && ppd_device_id[0] != '\0') + { + mfg = get_tag_value (ppd_device_id, "mfg"); + if (!mfg) + mfg = get_tag_value (ppd_device_id, "manufacturer"); + mfg_normalized = normalize (mfg); + } + + if (!mfg && + ppd_make && + ppd_make[0] != '\0') + { + mfg = g_strdup (ppd_make); + mfg_normalized = normalize (ppd_make); + } + + /* Get model */ + if (ppd_make_and_model && + ppd_make_and_model[0] != '\0') + { + mdl = g_strdup (ppd_make_and_model); + } + + if (!mdl && + ppd_product && + ppd_product[0] != '\0') + { + mdl = g_strdup (ppd_product); + } + + if (!mdl && + ppd_device_id && + ppd_device_id[0] != '\0') + { + mdl = get_tag_value (ppd_device_id, "mdl"); + if (!mdl) + mdl = get_tag_value (ppd_device_id, "model"); + } + + if (ppd_name && ppd_name[0] != '\0' && + mdl && mdl[0] != '\0' && + mfg && mfg[0] != '\0') + { + manufacturer_display_name = g_hash_table_lookup (manufacturers_hash, mfg_normalized); + if (!manufacturer_display_name) + { + g_hash_table_insert (manufacturers_hash, g_strdup (mfg_normalized), g_strdup (mfg)); + } + else + { + g_free (mfg_normalized); + mfg_normalized = normalize (manufacturer_display_name); + } + + item = g_new0 (PPDName, 1); + item->ppd_name = g_strdup (ppd_name); + item->ppd_display_name = g_strdup (mdl); + item->ppd_match_level = -1; + + list = g_hash_table_lookup (ppds_hash, mfg_normalized); + if (list) + { + list = g_list_append (list, item); + } + else + { + list = g_list_append (list, item); + g_hash_table_insert (ppds_hash, g_strdup (mfg_normalized), list); + } + } + + if (attr == NULL) + break; + } + } + + if (response) + ippDelete(response); + + if (ppds_hash && + manufacturers_hash) + { + GHashTableIter iter; + gpointer key; + gpointer value; + GList *ppd_item; + GList *sort_list = NULL; + GList *list_iter; + gchar *name; + + data->result = g_new0 (PPDList, 1); + data->result->num_of_manufacturers = g_hash_table_size (ppds_hash); + data->result->manufacturers = g_new0 (PPDManufacturerItem *, data->result->num_of_manufacturers); + + g_hash_table_iter_init (&iter, ppds_hash); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + sort_list = g_list_append (sort_list, g_strdup (key)); + } + + /* Sort list of manufacturers */ + sort_list = g_list_sort (sort_list, (GCompareFunc) g_strcmp0); + + /* + * Fill resulting list of lists (list of manufacturers where + * each item contains list of PPD names) + */ + i = 0; + for (list_iter = sort_list; list_iter; list_iter = list_iter->next) + { + name = (gchar *) list_iter->data; + value = g_hash_table_lookup (ppds_hash, name); + + data->result->manufacturers[i] = g_new0 (PPDManufacturerItem, 1); + data->result->manufacturers[i]->manufacturer_name = g_strdup (name); + data->result->manufacturers[i]->manufacturer_display_name = g_strdup (g_hash_table_lookup (manufacturers_hash, name)); + data->result->manufacturers[i]->num_of_ppds = g_list_length ((GList *) value); + data->result->manufacturers[i]->ppds = g_new0 (PPDName *, data->result->manufacturers[i]->num_of_ppds); + + for (ppd_item = (GList *) value, j = 0; ppd_item; ppd_item = ppd_item->next, j++) + { + data->result->manufacturers[i]->ppds[j] = ppd_item->data; + } + + g_list_free ((GList *) value); + + i++; + } + + g_list_free_full (sort_list, g_free); + g_hash_table_destroy (ppds_hash); + g_hash_table_destroy (manufacturers_hash); + } + + get_all_ppds_cb (data); + + return NULL; +} + +/* + * Get names of all installed PPDs sorted by manufacturers names. + */ +void +get_all_ppds_async (GCancellable *cancellable, + GAPCallback callback, + gpointer user_data) +{ + GAPData *data; + GThread *thread; + g_autoptr(GError) error = NULL; + + data = g_new0 (GAPData, 1); + if (cancellable) + data->cancellable = g_object_ref (cancellable); + data->callback = callback; + data->user_data = user_data; + data->context = g_main_context_ref_thread_default (); + + thread = g_thread_try_new ("get-all-ppds", + get_all_ppds_func, + data, + &error); + + if (!thread) + { + g_warning ("%s", error->message); + callback (NULL, user_data); + + get_all_ppds_data_free (data); + } + else + { + g_thread_unref (thread); + } +} + +PPDList * +ppd_list_copy (PPDList *list) +{ + PPDList *result = NULL; + gint i, j; + + if (list) + { + result = g_new0 (PPDList, 1); + result->num_of_manufacturers = list->num_of_manufacturers; + result->manufacturers = g_new0 (PPDManufacturerItem *, list->num_of_manufacturers); + + for (i = 0; i < result->num_of_manufacturers; i++) + { + result->manufacturers[i] = g_new0 (PPDManufacturerItem, 1); + result->manufacturers[i]->num_of_ppds = list->manufacturers[i]->num_of_ppds; + result->manufacturers[i]->ppds = g_new0 (PPDName *, result->manufacturers[i]->num_of_ppds); + + result->manufacturers[i]->manufacturer_display_name = + g_strdup (list->manufacturers[i]->manufacturer_display_name); + + result->manufacturers[i]->manufacturer_name = + g_strdup (list->manufacturers[i]->manufacturer_name); + + for (j = 0; j < result->manufacturers[i]->num_of_ppds; j++) + { + result->manufacturers[i]->ppds[j] = g_new0 (PPDName, 1); + + result->manufacturers[i]->ppds[j]->ppd_display_name = + g_strdup (list->manufacturers[i]->ppds[j]->ppd_display_name); + + result->manufacturers[i]->ppds[j]->ppd_name = + g_strdup (list->manufacturers[i]->ppds[j]->ppd_name); + + result->manufacturers[i]->ppds[j]->ppd_match_level = + list->manufacturers[i]->ppds[j]->ppd_match_level; + } + } + } + + return result; +} + +void +ppd_list_free (PPDList *list) +{ + gint i, j; + + if (list) + { + for (i = 0; i < list->num_of_manufacturers; i++) + { + for (j = 0; j < list->manufacturers[i]->num_of_ppds; j++) + { + g_free (list->manufacturers[i]->ppds[j]->ppd_name); + g_free (list->manufacturers[i]->ppds[j]->ppd_display_name); + g_free (list->manufacturers[i]->ppds[j]); + } + + g_free (list->manufacturers[i]->manufacturer_name); + g_free (list->manufacturers[i]->manufacturer_display_name); + g_free (list->manufacturers[i]->ppds); + g_free (list->manufacturers[i]); + } + + g_free (list->manufacturers); + g_free (list); + } +} + +gchar * +get_standard_manufacturers_name (const gchar *name) +{ + g_autofree gchar *normalized_name = NULL; + gint i; + + if (name == NULL) + return NULL; + + normalized_name = normalize (name); + + for (i = 0; i < G_N_ELEMENTS (manufacturers_names); i++) + { + if (g_strcmp0 (manufacturers_names[i].normalized_name, normalized_name) == 0) + { + return g_strdup (manufacturers_names[i].display_name); + } + } + + return NULL; +} + +typedef struct +{ + gchar *printer_name; + gchar *host_name; + gint port; + gchar *result; + PGPCallback callback; + gpointer user_data; + GMainContext *context; +} PGPData; + +static gboolean +printer_get_ppd_idle_cb (gpointer user_data) +{ + PGPData *data = (PGPData *) user_data; + + data->callback (data->result, data->user_data); + + return FALSE; +} + +static void +printer_get_ppd_data_free (gpointer user_data) +{ + PGPData *data = (PGPData *) user_data; + + if (data->context) + g_main_context_unref (data->context); + g_free (data->result); + g_free (data->printer_name); + g_free (data->host_name); + g_free (data); +} + +static void +printer_get_ppd_cb (gpointer user_data) +{ + PGPData *data = (PGPData *) user_data; + GSource *idle_source; + + idle_source = g_idle_source_new (); + g_source_set_callback (idle_source, + printer_get_ppd_idle_cb, + data, + printer_get_ppd_data_free); + g_source_attach (idle_source, data->context); + g_source_unref (idle_source); +} + +static gpointer +printer_get_ppd_func (gpointer user_data) +{ + PGPData *data = (PGPData *) user_data; + + if (data->host_name) + { + http_t *http; + +#ifdef HAVE_CUPS_HTTPCONNECT2 + http = httpConnect2 (data->host_name, data->port, NULL, AF_UNSPEC, + HTTP_ENCRYPTION_IF_REQUESTED, 1, 30000, NULL); +#else + http = httpConnect (data->host_name, data->port); +#endif + if (http) + { + data->result = g_strdup (cupsGetPPD2 (http, data->printer_name)); + httpClose (http); + } + } + else + { + data->result = g_strdup (cupsGetPPD (data->printer_name)); + } + + printer_get_ppd_cb (data); + + return NULL; +} + +void +printer_get_ppd_async (const gchar *printer_name, + const gchar *host_name, + gint port, + PGPCallback callback, + gpointer user_data) +{ + PGPData *data; + GThread *thread; + g_autoptr(GError) error = NULL; + + data = g_new0 (PGPData, 1); + data->printer_name = g_strdup (printer_name); + data->host_name = g_strdup (host_name); + data->port = port; + data->callback = callback; + data->user_data = user_data; + data->context = g_main_context_ref_thread_default (); + + thread = g_thread_try_new ("printer-get-ppd", + printer_get_ppd_func, + data, + &error); + + if (!thread) + { + g_warning ("%s", error->message); + callback (NULL, user_data); + + printer_get_ppd_data_free (data); + } + else + { + g_thread_unref (thread); + } +} + +typedef struct +{ + gchar *printer_name; + cups_dest_t *result; + GNDCallback callback; + gpointer user_data; + GMainContext *context; +} GNDData; + +static gboolean +get_named_dest_idle_cb (gpointer user_data) +{ + GNDData *data = (GNDData *) user_data; + + data->callback (data->result, data->user_data); + + return FALSE; +} + +static void +get_named_dest_data_free (gpointer user_data) +{ + GNDData *data = (GNDData *) user_data; + + if (data->context) + g_main_context_unref (data->context); + g_free (data->printer_name); + g_free (data); +} + +static void +get_named_dest_cb (gpointer user_data) +{ + GNDData *data = (GNDData *) user_data; + GSource *idle_source; + + idle_source = g_idle_source_new (); + g_source_set_callback (idle_source, + get_named_dest_idle_cb, + data, + get_named_dest_data_free); + g_source_attach (idle_source, data->context); + g_source_unref (idle_source); +} + +static gpointer +get_named_dest_func (gpointer user_data) +{ + GNDData *data = (GNDData *) user_data; + + data->result = cupsGetNamedDest (CUPS_HTTP_DEFAULT, data->printer_name, NULL); + + get_named_dest_cb (data); + + return NULL; +} + +void +get_named_dest_async (const gchar *printer_name, + GNDCallback callback, + gpointer user_data) +{ + GNDData *data; + GThread *thread; + g_autoptr(GError) error = NULL; + + data = g_new0 (GNDData, 1); + data->printer_name = g_strdup (printer_name); + data->callback = callback; + data->user_data = user_data; + data->context = g_main_context_ref_thread_default (); + + thread = g_thread_try_new ("get-named-dest", + get_named_dest_func, + data, + &error); + + if (!thread) + { + g_warning ("%s", error->message); + callback (NULL, user_data); + + get_named_dest_data_free (data); + } + else + { + g_thread_unref (thread); + } +} + +typedef struct +{ + GCancellable *cancellable; + PAOCallback callback; + gpointer user_data; +} PAOData; + +static void +printer_add_option_async_dbus_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GVariant) output = NULL; + gboolean success = FALSE; + PAOData *data = (PAOData *) user_data; + g_autoptr(GError) error = NULL; + + output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), + res, + &error); + g_object_unref (source_object); + + if (output) + { + const gchar *ret_error; + + g_variant_get (output, "(&s)", &ret_error); + if (ret_error[0] != '\0') + g_warning ("cups-pk-helper: setting of an option failed: %s", ret_error); + else + success = TRUE; + } + else + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("%s", error->message); + } + + if (!g_cancellable_is_cancelled (data->cancellable)) + data->callback (success, data->user_data); + + if (data->cancellable) + g_object_unref (data->cancellable); + g_free (data); +} + +void +printer_add_option_async (const gchar *printer_name, + const gchar *option_name, + gchar **values, + gboolean set_default, + GCancellable *cancellable, + PAOCallback callback, + gpointer user_data) +{ + GVariantBuilder array_builder; + GDBusConnection *bus; + PAOData *data; + g_autoptr(GError) error = NULL; + gint i; + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (!bus) + { + g_warning ("Failed to get system bus: %s", error->message); + callback (FALSE, user_data); + return; + } + + g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("as")); + if (values) + { + for (i = 0; values[i]; i++) + g_variant_builder_add (&array_builder, "s", values[i]); + } + + data = g_new0 (PAOData, 1); + if (cancellable) + data->cancellable = g_object_ref (cancellable); + data->callback = callback; + data->user_data = user_data; + + g_dbus_connection_call (bus, + MECHANISM_BUS, + "/", + MECHANISM_BUS, + set_default ? "PrinterAddOptionDefault" : + "PrinterAddOption", + g_variant_new ("(ssas)", + printer_name, + option_name, + &array_builder), + G_VARIANT_TYPE ("(s)"), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT, + cancellable, + printer_add_option_async_dbus_cb, + data); +} + +typedef struct +{ + GCancellable *cancellable; + GCDCallback callback; + gpointer user_data; + GList *backend_list; +} GCDData; + +static gint +get_suffix_index (const gchar *string) +{ + gchar *number; + gchar *endptr; + gint index = -1; + + number = g_strrstr (string, ":"); + if (number) + { + number++; + index = g_ascii_strtoll (number, &endptr, 10); + if (index == 0 && endptr == number) + index = -1; + } + + return index; +} + +static void +get_cups_devices_async_dbus_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) + +{ + g_autoptr(GPtrArray) devices = NULL; + g_autoptr(GVariant) output = NULL; + GCDData *data = (GCDData *) user_data; + g_autoptr(GError) error = NULL; + gint num_of_devices = 0; + + output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), + res, + &error); + + if (output) + { + const gchar *ret_error; + g_autoptr(GVariant) devices_variant = NULL; + gboolean is_network_device; + g_autoptr(GVariantIter) iter = NULL; + const gchar *key, *value; + gint index = -1, max_index = -1, i; + + g_variant_get (output, "(&s@a{ss})", + &ret_error, + &devices_variant); + + if (ret_error[0] != '\0') + { + g_warning ("cups-pk-helper: getting of CUPS devices failed: %s", ret_error); + } + + g_variant_get (devices_variant, "a{ss}", &iter); + while (g_variant_iter_next (iter, "{&s&s}", &key, &value)) + { + index = get_suffix_index (key); + if (index > max_index) + max_index = index; + } + + if (max_index >= 0) + { + g_autoptr(GVariantIter) iter2 = NULL; + + num_of_devices = max_index + 1; + devices = g_ptr_array_new_with_free_func (g_object_unref); + for (i = 0; i < num_of_devices; i++) + g_ptr_array_add (devices, pp_print_device_new ()); + + g_variant_get (devices_variant, "a{ss}", &iter2); + while (g_variant_iter_next (iter2, "{&s&s}", &key, &value)) + { + PpPrintDevice *device; + + index = get_suffix_index (key); + if (index >= 0) + { + device = g_ptr_array_index (devices, index); + if (g_str_has_prefix (key, "device-class")) + { + is_network_device = g_strcmp0 (value, "network") == 0; + g_object_set (device, "is-network-device", is_network_device, NULL); + } + else if (g_str_has_prefix (key, "device-id")) + g_object_set (device, "device-id", value, NULL); + else if (g_str_has_prefix (key, "device-info")) + g_object_set (device, "device-info", value, NULL); + else if (g_str_has_prefix (key, "device-make-and-model")) + { + g_object_set (device, + "device-make-and-model", value, + "device-name", value, + NULL); + } + else if (g_str_has_prefix (key, "device-uri")) + g_object_set (device, "device-uri", value, NULL); + else if (g_str_has_prefix (key, "device-location")) + g_object_set (device, "device-location", value, NULL); + + g_object_set (device, "acquisition-method", ACQUISITION_METHOD_DEFAULT_CUPS_SERVER, NULL); + } + } + } + } + else + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("%s", error->message); + + data->callback (devices, + TRUE, + g_cancellable_is_cancelled (data->cancellable), + data->user_data); + + g_list_free_full (data->backend_list, g_free); + data->backend_list = NULL; + g_object_unref (source_object); + if (data->cancellable) + g_object_unref (data->cancellable); + g_free (data); + + return; + } + + if (data->backend_list) + { + if (!g_cancellable_is_cancelled (data->cancellable)) + { + GVariantBuilder *include_scheme_builder = NULL; + GVariantBuilder *exclude_scheme_builder = NULL; + g_autofree gchar *backend_name = NULL; + + backend_name = data->backend_list->data; + + data->callback (devices, + FALSE, + FALSE, + data->user_data); + + if (g_strcmp0 (backend_name, OTHER_BACKENDS) != 0) + { + include_scheme_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); + g_variant_builder_add (include_scheme_builder, "s", backend_name); + } + else + { + exclude_scheme_builder = create_other_backends_array (); + } + + data->backend_list = g_list_remove_link (data->backend_list, data->backend_list); + + g_dbus_connection_call (G_DBUS_CONNECTION (g_object_ref (source_object)), + MECHANISM_BUS, + "/", + MECHANISM_BUS, + "DevicesGet", + g_variant_new ("(iiasas)", + 0, + 0, + include_scheme_builder, + exclude_scheme_builder), + G_VARIANT_TYPE ("(sa{ss})"), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT, + data->cancellable, + get_cups_devices_async_dbus_cb, + user_data); + + if (include_scheme_builder) + g_variant_builder_unref (include_scheme_builder); + + if (exclude_scheme_builder) + g_variant_builder_unref (exclude_scheme_builder); + + return; + } + else + { + data->callback (devices, + TRUE, + TRUE, + data->user_data); + + g_list_free_full (data->backend_list, g_free); + data->backend_list = NULL; + } + } + else + { + data->callback (devices, + TRUE, + g_cancellable_is_cancelled (data->cancellable), + data->user_data); + } + + g_object_unref (source_object); + if (data->cancellable) + g_object_unref (data->cancellable); + g_free (data); +} + +void +get_cups_devices_async (GCancellable *cancellable, + GCDCallback callback, + gpointer user_data) +{ + GDBusConnection *bus; + GVariantBuilder include_scheme_builder; + GCDData *data; + g_autoptr(GError) error = NULL; + g_autofree gchar *backend_name = NULL; + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (!bus) + { + g_warning ("Failed to get system bus: %s", error->message); + callback (NULL, TRUE, FALSE, user_data); + return; + } + + data = g_new0 (GCDData, 1); + if (cancellable) + data->cancellable = g_object_ref (cancellable); + data->callback = callback; + data->user_data = user_data; + data->backend_list = create_backends_list (); + + backend_name = data->backend_list->data; + + g_variant_builder_init (&include_scheme_builder, G_VARIANT_TYPE ("as")); + g_variant_builder_add (&include_scheme_builder, "s", backend_name); + + data->backend_list = g_list_remove_link (data->backend_list, data->backend_list); + + g_dbus_connection_call (bus, + MECHANISM_BUS, + "/", + MECHANISM_BUS, + "DevicesGet", + g_variant_new ("(iiasas)", + 0, + 0, + &include_scheme_builder, + NULL), + G_VARIANT_TYPE ("(sa{ss})"), + G_DBUS_CALL_FLAGS_NONE, + DBUS_TIMEOUT, + cancellable, + get_cups_devices_async_dbus_cb, + data); +} + +gchar * +guess_device_hostname (PpPrintDevice *device) +{ + http_uri_status_t status; + char scheme[HTTP_MAX_URI]; + char username[HTTP_MAX_URI]; + char hostname[HTTP_MAX_URI]; + char resource[HTTP_MAX_URI]; + int port; + gchar *result = NULL; + gchar *hostname_begin; + gchar *hostname_end = NULL; + + if (device != NULL && pp_print_device_get_device_uri (device) != NULL) + { + if (g_str_has_prefix (pp_print_device_get_device_uri (device), "socket") || + g_str_has_prefix (pp_print_device_get_device_uri (device), "lpd") || + g_str_has_prefix (pp_print_device_get_device_uri (device), "ipp") || + g_str_has_prefix (pp_print_device_get_device_uri (device), "smb")) + { + status = httpSeparateURI (HTTP_URI_CODING_ALL, + pp_print_device_get_device_uri (device), + scheme, HTTP_MAX_URI, + username, HTTP_MAX_URI, + hostname, HTTP_MAX_URI, + &port, + resource, HTTP_MAX_URI); + + if (status >= HTTP_URI_STATUS_OK && + hostname[0] != '\0') + result = g_strdup (hostname); + } + else if ((g_str_has_prefix (pp_print_device_get_device_uri (device), "dnssd") || + g_str_has_prefix (pp_print_device_get_device_uri (device), "mdns")) && + pp_print_device_get_device_info (device) != NULL) + { + /* + * CUPS browses its printers as + * "PrinterName @ ComputerName" or "PrinterInfo @ ComputerName" + * through DNS-SD. + */ + hostname_begin = g_strrstr (pp_print_device_get_device_info (device), " @ "); + if (hostname_begin != NULL) + result = g_strdup (hostname_begin + 3); + } + else if (g_str_has_prefix (pp_print_device_get_device_uri (device), "hp:/net/") || + g_str_has_prefix (pp_print_device_get_device_uri (device), "hpfax:/net/")) + { + /* + * HPLIP printers have URI of form hp:/net/%s?ip=%s&port=%d + * or hp:/net/%s?ip=%s. + */ + hostname_begin = g_strrstr (pp_print_device_get_device_uri (device), "ip="); + if (hostname_begin != NULL) + { + hostname_begin += 3; + hostname_end = strstr (hostname_begin, "&"); + } + + if (hostname_end != NULL) + result = g_strndup (hostname_begin, hostname_end - hostname_begin); + else + result = g_strdup (hostname_begin); + } + } + + return result; +} + +gchar * +canonicalize_device_name (GList *device_names, + GPtrArray *local_cups_devices, + cups_dest_t *dests, + gint num_of_dests, + PpPrintDevice *device) +{ + PpPrintDevice *item; + gboolean already_present; + GList *iter; + gsize len; + g_autofree gchar *name = NULL; + gchar *occurrence; + gint name_index, j; + static const char * const residues[] = { + "-foomatic", + "-hpijs", + "-hpcups", + "-cups", + "-gutenprint", + "-series", + "-label-printer", + "-dot-matrix", + "-ps3", + "-ps2", + "-br-script", + "-kpdl", + "-pcl3", + "-pcl", + "-zxs", + "-pxl"}; + + if (pp_print_device_get_device_id (device) != NULL) + { + name = get_tag_value (pp_print_device_get_device_id (device), "mdl"); + if (name == NULL) + name = get_tag_value (pp_print_device_get_device_id (device), "model"); + } + + if (name == NULL && + pp_print_device_get_device_make_and_model (device) != NULL && + pp_print_device_get_device_make_and_model (device)[0] != '\0') + { + name = g_strdup (pp_print_device_get_device_make_and_model (device)); + } + + if (name == NULL && + pp_print_device_get_device_original_name (device) != NULL && + pp_print_device_get_device_original_name (device)[0] != '\0') + { + name = g_strdup (pp_print_device_get_device_original_name (device)); + } + + if (name == NULL && + pp_print_device_get_device_info (device) != NULL && + pp_print_device_get_device_info (device)[0] != '\0') + { + name = g_strdup (pp_print_device_get_device_info (device)); + } + + if (name == NULL) + return NULL; + + g_strstrip (name); + g_strcanon (name, ALLOWED_CHARACTERS, '-'); + + /* Remove common strings found in driver names */ + for (j = 0; j < G_N_ELEMENTS (residues); j++) + { + g_autofree gchar *lower_name = g_ascii_strdown (name, -1); + + occurrence = g_strrstr (lower_name, residues[j]); + if (occurrence != NULL) + { + occurrence[0] = '\0'; + name[strlen (lower_name)] = '\0'; + } + } + + /* Remove trailing "-" */ + len = strlen (name); + while (len-- && name[len] == '-') + name[len] = '\0'; + + /* Merge "--" to "-" */ + occurrence = g_strrstr (name, "--"); + while (occurrence != NULL) + { + shift_string_left (occurrence); + occurrence = g_strrstr (name, "--"); + } + + /* Remove leading "-" */ + if (name[0] == '-') + shift_string_left (name); + + name_index = 2; + already_present = FALSE; + while (TRUE) + { + g_autofree gchar *new_name = NULL; + + if (already_present) + { + new_name = g_strdup_printf ("%s-%d", name, name_index); + name_index++; + } + else + { + new_name = g_strdup (name); + } + + already_present = FALSE; + for (j = 0; j < num_of_dests; j++) + if (g_strcmp0 (dests[j].name, new_name) == 0) + already_present = TRUE; + + for (iter = device_names; iter; iter = iter->next) + { + gchar *device_original_name = iter->data; + if (g_strcmp0 (device_original_name, new_name) == 0) + already_present = TRUE; + } + + for (guint i = 0; i < local_cups_devices->len; i++) + { + item = g_ptr_array_index (local_cups_devices, i); + if (g_strcmp0 (pp_print_device_get_device_original_name (item), new_name) == 0) + already_present = TRUE; + } + + if (!already_present) + return g_steal_pointer (&new_name); + } +} + +void +shift_string_left (gchar *str) +{ + gchar *next; + + if (str != NULL && str[0] != '\0') + { + next = g_utf8_find_next_char (str, NULL); + memmove (str, next, strlen (next) + 1); + } +} |