/* -*- 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 . * */ #include "config.h" #include #include #include #include #include #include #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 GIAData * gia_data_new (const gchar *printer_name, gchar **attributes_names, GIACallback callback, gpointer user_data) { GIAData *data; 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 (); return data; } static void gia_data_free (GIAData *data) { g_free (data->printer_name); if (data->attributes_names) g_strfreev (data->attributes_names); if (data->result) g_hash_table_unref (data->result); if (data->context) g_main_context_unref (data->context); g_free (data); } 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_cb (gpointer user_data) { GIAData *data = user_data; g_autoptr(GSource) idle_source = NULL; idle_source = g_idle_source_new (); g_source_set_callback (idle_source, get_ipp_attributes_idle_cb, data, (GDestroyNotify) gia_data_free); g_source_attach (idle_source, data->context); } 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 = 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; g_autoptr(GThread) thread = NULL; g_autoptr(GError) error = NULL; data = gia_data_new (printer_name, attributes_names, callback, user_data); 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); gia_data_free (data); } } 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 PSPData * psp_data_new (const gchar *printer_name, const gchar *ppd_copy, GCancellable *cancellable, PSPCallback callback, gpointer user_data) { PSPData *data; data = g_new0 (PSPData, 1); data->printer_name = g_strdup (printer_name); data->ppd_copy = g_strdup (ppd_copy); if (cancellable) data->cancellable = g_object_ref (cancellable); data->callback = callback; data->user_data = user_data; return data; } static void psp_data_free (PSPData *data) { g_free (data->printer_name); if (data->ppd_copy != NULL) { g_unlink (data->ppd_copy); g_free (data->ppd_copy); } g_clear_object (&data->cancellable); g_free (data); } G_DEFINE_AUTOPTR_CLEANUP_FUNC (PSPData, psp_data_free) static void printer_set_ppd_async_dbus_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { g_autoptr(GVariant) output = NULL; gboolean result = FALSE; g_autoptr(PSPData) data = user_data; g_autoptr(GError) error = NULL; output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error); 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); } /* * 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) { g_autoptr(GDBusConnection) bus = NULL; g_autoptr(GError) error = NULL; if (printer_name == NULL || printer_name[0] == '\0') { callback (printer_name, FALSE, user_data); return; } bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); if (!bus) { g_warning ("Failed to get system bus: %s", error->message); callback (printer_name, FALSE, user_data); return; } 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, cancellable, printer_set_ppd_async_dbus_cb, psp_data_new (printer_name, NULL, cancellable, callback, user_data)); } static void printer_set_ppd_file_async_scb (GObject *source_object, GAsyncResult *res, gpointer user_data) { g_autoptr(GDBusConnection) bus = NULL; gboolean success; g_autoptr(PSPData) data = user_data; g_autoptr(GError) error = NULL; success = g_file_copy_finish (G_FILE (source_object), res, &error); if (!success) { g_warning ("%s", error->message); data->callback (data->printer_name, FALSE, data->user_data); return; } bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); if (!bus) { g_warning ("Failed to get system bus: %s", error->message); data->callback (data->printer_name, FALSE, data->user_data); return; } 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); g_steal_pointer (&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) { g_autoptr(GFileIOStream) stream = NULL; g_autoptr(GFile) source_ppd_file = NULL; g_autoptr(GFile) destination_ppd_file = NULL; if (printer_name == NULL || printer_name[0] == '\0') { callback (printer_name, FALSE, user_data); return; } /* * 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_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, psp_data_new (printer_name, g_file_get_path (destination_ppd_file), cancellable, callback, user_data)); } typedef void (*GPACallback) (gchar **attribute_values, gpointer user_data); typedef struct { gchar **ppds_names; gchar *attribute_name; gchar **result; GPACallback callback; gpointer user_data; GMainContext *context; } GPAData; static GPAData * gpa_data_new (gchar **ppds_names, gchar *attribute_name, GPACallback callback, gpointer user_data) { GPAData *data; 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 (); return data; } static void gpa_data_free (GPAData *data) { g_free (data->attribute_name); g_strfreev (data->ppds_names); if (data->result != NULL) g_strfreev (data->result); if (data->context) g_main_context_unref (data->context); g_free (data); } 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_cb (gpointer user_data) { GPAData *data = (GPAData *) user_data; g_autoptr(GSource) idle_source = NULL; idle_source = g_idle_source_new (); g_source_set_callback (idle_source, get_ppds_attribute_idle_cb, data, (GDestroyNotify) gpa_data_free); g_source_attach (idle_source, data->context); } static gpointer get_ppds_attribute_func (gpointer user_data) { ppd_file_t *ppd_file; ppd_attr_t *ppd_attr; GPAData *data = 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; g_autoptr(GThread) thread = NULL; g_autoptr(GError) error = NULL; if (!ppds_names || !attribute_name) { callback (NULL, user_data); return; } data = gpa_data_new (ppds_names, attribute_name, callback, user_data); 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); gpa_data_free (data); } } 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; GList *backend_list; GCancellable *cancellable; GDACallback callback; gpointer user_data; } GDAData; static GDAData * gda_data_new (const gchar *printer_name, GCancellable *cancellable, GDACallback callback, gpointer user_data) { GDAData *data; 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; return data; } static void gda_data_free (GDAData *data) { g_free (data->printer_name); g_free (data->device_uri); g_list_free_full(data->backend_list, g_free); g_clear_object (&data->cancellable); g_free (data); } G_DEFINE_AUTOPTR_CLEANUP_FUNC (GDAData, gda_data_free) typedef struct { gchar *printer_name; gint count; PPDName **result; GCancellable *cancellable; GPNCallback callback; gpointer user_data; } GPNData; static GPNData * gpn_data_new (const gchar *printer_name, gint count, GCancellable *cancellable, GPNCallback callback, gpointer user_data) { GPNData *data; 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; return data; } static void gpn_data_free (GPNData *data) { g_free (data->printer_name); if (data->result != NULL) { for (int i = 0; data->result[i]; i++) { g_free (data->result[i]->ppd_name); g_free (data->result[i]->ppd_display_name); g_free (data->result[i]); } g_free (data->result); } g_clear_object (&data->cancellable); g_free (data); } G_DEFINE_AUTOPTR_CLEANUP_FUNC (GPNData, gpn_data_free) static void get_ppd_names_async_cb (gchar **attribute_values, gpointer user_data) { g_autoptr(GPNData) data = user_data; gint i; if (g_cancellable_is_cancelled (data->cancellable)) { data->callback (NULL, data->printer_name, TRUE, data->user_data); return; } if (attribute_values) { for (i = 0; attribute_values[i]; i++) data->result[i]->ppd_display_name = g_strdup (attribute_values[i]); } data->callback (data->result, data->printer_name, FALSE, data->user_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; g_autoptr(GPNData) data = 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); 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) { g_auto(GStrv) ppds_names = NULL; 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_steal_pointer (&data); } else { data->callback (NULL, data->printer_name, g_cancellable_is_cancelled (data->cancellable), data->user_data); } } static void get_device_attributes_cb (gchar *device_id, gchar *device_make_and_model, gchar *device_uri, gpointer user_data) { g_autoptr(GDBusConnection) bus = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPNData) data = user_data; if (g_cancellable_is_cancelled (data->cancellable)) { data->callback (NULL, data->printer_name, TRUE, data->user_data); return; } if (!device_id || !device_make_and_model || !device_uri) { data->callback (NULL, data->printer_name, FALSE, data->user_data); return; } bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); if (!bus) { g_warning ("Failed to get system bus: %s", error->message); data->callback (NULL, data->printer_name, FALSE, data->user_data); return; } 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); g_steal_pointer (&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; g_autoptr(GDAData) data = user_data; g_autoptr(GError) error = NULL; gchar *device_id = NULL; gchar *device_make_and_model = NULL; 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; 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 (); } data->backend_list = g_list_delete_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_device_attributes_async_dbus_cb, data); g_steal_pointer (&data); if (include_scheme_builder) g_variant_builder_unref (include_scheme_builder); if (exclude_scheme_builder) g_variant_builder_unref (exclude_scheme_builder); return; } } data->callback (device_id, device_make_and_model, data->device_uri, data->user_data); } static void get_device_attributes_async_scb (GHashTable *result, gpointer user_data) { g_autoptr(GDBusConnection) bus = NULL; GVariantBuilder include_scheme_builder; IPPAttribute *attr; g_autoptr(GDAData) data = user_data; g_autoptr(GError) error = NULL; 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)) { data->callback (NULL, NULL, NULL, data->user_data); return; } if (!data->device_uri) { data->callback (NULL, NULL, NULL, data->user_data); return; } bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); if (!bus) { g_warning ("Failed to get system bus: %s", error->message); data->callback (NULL, NULL, NULL, data->user_data); return; } 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); data->backend_list = g_list_delete_link (data->backend_list, data->backend_list); 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); g_steal_pointer (&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) { g_auto(GStrv) attributes = NULL; if (!printer_name) { callback (NULL, NULL, NULL, user_data); return; } attributes = g_new0 (gchar *, 2); attributes[0] = g_strdup ("device-uri"); get_ipp_attributes_async (printer_name, attributes, get_device_attributes_async_scb, gda_data_new (printer_name, cancellable, callback, user_data)); } /* * 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) { if (!printer_name) { callback (NULL, NULL, TRUE, user_data); return; } /* * We have to find out device-id for this printer at first. */ get_device_attributes_async (printer_name, cancellable, get_device_attributes_cb, gpn_data_new (printer_name, count, cancellable, callback, user_data)); } typedef struct { PPDList *result; GCancellable *cancellable; GAPCallback callback; gpointer user_data; GMainContext *context; } GAPData; static GAPData * gap_data_new (GCancellable *cancellable, GAPCallback callback, gpointer user_data) { GAPData *data; 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 (); return data; } static void gap_data_free (GAPData *data) { if (data->result != NULL) ppd_list_free (data->result); g_clear_object (&data->cancellable); if (data->context) g_main_context_unref (data->context); g_free (data); } static gboolean get_all_ppds_idle_cb (gpointer user_data) { GAPData *data = user_data; if (!g_cancellable_is_cancelled (data->cancellable)) data->callback (data->result, data->user_data); return FALSE; } static void get_all_ppds_cb (gpointer user_data) { GAPData *data = user_data; g_autoptr(GSource) idle_source = NULL; idle_source = g_idle_source_new (); g_source_set_callback (idle_source, get_all_ppds_idle_cb, data, (GDestroyNotify) gap_data_free); g_source_attach (idle_source, data->context); } 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 = 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; g_autoptr(GThread) thread = NULL; g_autoptr(GError) error = NULL; data = gap_data_new (cancellable, callback, user_data); 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); gap_data_free (data); } } 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 PGPData * pgp_data_new (const gchar *printer_name, const gchar *host_name, gint port, PGPCallback callback, gpointer user_data) { PGPData *data; 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 (); return data; } static void pgp_data_free (PGPData *data) { g_free (data->printer_name); g_free (data->host_name); g_free (data->result); if (data->context) g_main_context_unref (data->context); g_free (data); } static gboolean printer_get_ppd_idle_cb (gpointer user_data) { PGPData *data = user_data; data->callback (data->result, data->user_data); return FALSE; } static void printer_get_ppd_cb (gpointer user_data) { PGPData *data = user_data; g_autoptr(GSource) idle_source = NULL; idle_source = g_idle_source_new (); g_source_set_callback (idle_source, printer_get_ppd_idle_cb, data, (GDestroyNotify) pgp_data_free); g_source_attach (idle_source, data->context); } static gpointer printer_get_ppd_func (gpointer user_data) { PGPData *data = 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; g_autoptr(GThread) thread = NULL; g_autoptr(GError) error = NULL; data = pgp_data_new (printer_name, host_name, port, callback, user_data); 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); pgp_data_free (data); } } typedef struct { gchar *printer_name; cups_dest_t *result; GNDCallback callback; gpointer user_data; GMainContext *context; } GNDData; static GNDData * gnd_data_new (const gchar *printer_name, GNDCallback callback, gpointer user_data) { GNDData *data; 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 (); return data; } static void gnd_data_free (GNDData *data) { g_free (data->printer_name); if (data->context) g_main_context_unref (data->context); g_free (data); } static gboolean get_named_dest_idle_cb (gpointer user_data) { GNDData *data = user_data; data->callback (data->result, data->user_data); return FALSE; } static void get_named_dest_cb (gpointer user_data) { GNDData *data = user_data; g_autoptr(GSource) idle_source = NULL; idle_source = g_idle_source_new (); g_source_set_callback (idle_source, get_named_dest_idle_cb, data, (GDestroyNotify) gnd_data_free); g_source_attach (idle_source, data->context); } static gpointer get_named_dest_func (gpointer user_data) { GNDData *data = 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; g_autoptr(GThread) thread = NULL; g_autoptr(GError) error = NULL; data = gnd_data_new (printer_name, callback, user_data); 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); gnd_data_free (data); } } typedef struct { GCancellable *cancellable; PAOCallback callback; gpointer user_data; } PAOData; static PAOData * pao_data_new (GCancellable *cancellable, PAOCallback callback, gpointer user_data) { PAOData *data; data = g_new0 (PAOData, 1); if (cancellable) data->cancellable = g_object_ref (cancellable); data->callback = callback; data->user_data = user_data; return data; } static void pao_data_free (PAOData *data) { g_clear_object (&data->cancellable); g_free (data); } G_DEFINE_AUTOPTR_CLEANUP_FUNC (PAOData, pao_data_free) static void printer_add_option_async_dbus_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { g_autoptr(GVariant) output = NULL; gboolean success = FALSE; g_autoptr(PAOData) data = user_data; g_autoptr(GError) error = NULL; output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error); 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); } 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; g_autoptr(GDBusConnection) bus = NULL; 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]); } 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, pao_data_new (cancellable, callback, user_data)); } typedef struct { GList *backend_list; GCancellable *cancellable; GCDCallback callback; gpointer user_data; } GCDData; static GCDData * gcd_data_new (GList *backend_list, GCancellable *cancellable, GCDCallback callback, gpointer user_data) { GCDData *data; data = g_new0 (GCDData, 1); data->backend_list = backend_list; if (cancellable) data->cancellable = g_object_ref (cancellable); data->callback = callback; data->user_data = user_data; return data; } static void gcd_data_free (GCDData *data) { g_list_free_full (data->backend_list, g_free); g_clear_object (&data->cancellable); g_free (data); } G_DEFINE_AUTOPTR_CLEANUP_FUNC (GCDData, gcd_data_free) 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; g_autoptr(GCDData) data = 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); 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_delete_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, data); g_steal_pointer (&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); } } else { data->callback (devices, TRUE, g_cancellable_is_cancelled (data->cancellable), data->user_data); } } void get_cups_devices_async (GCancellable *cancellable, GCDCallback callback, gpointer user_data) { g_autoptr(GDBusConnection) bus = NULL; GVariantBuilder include_scheme_builder; GList *backend_list; 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; } backend_list = create_backends_list (); backend_name = backend_list->data; g_variant_builder_init (&include_scheme_builder, G_VARIANT_TYPE ("as")); g_variant_builder_add (&include_scheme_builder, "s", backend_name); backend_list = g_list_delete_link (backend_list, 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, gcd_data_new (backend_list, cancellable, callback, user_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); } } gboolean printer_name_is_valid (const gchar *str) { const gchar *invalid_chars = " \t#/"; return strlen(str) == strcspn(str, invalid_chars); }