Commit eed9e72e authored by Marek Kašík's avatar Marek Kašík

printing: List Avahi printers

Show printers advertised by avahi on local network. CUPS
backend now looks for _ipps._tcp and _ipp._tcp services
offered by avahi. If it finds such a service (printer)
it requests its attributes through IPP_GET_PRINTER_ATTRIBUTES
ipp request and adds it to the list of printers. Such printer
behaves like a remote printer then.
If an avahi printer is a default printer then it is considered
default by the backend only if there is no local or remote
default printer.
This functionality is enabled when building Gtk+ with CUPS 1.6
or later because it replaces browsing protocol removed in CUPS 1.6.

https://bugzilla.gnome.org/show_bug.cgi?id=688956
parent bfb327de
......@@ -1421,7 +1421,7 @@ else
$CUPS_API_MAJOR -eq 1 -a $CUPS_API_MINOR -ge 6; then
AC_DEFINE(HAVE_CUPS_API_1_6, 1,
[Define to 1 if CUPS 1.6 API is available])
have_cups_api_1_6=yes
fi
AC_SUBST(CUPS_API_MAJOR)
......@@ -1452,6 +1452,45 @@ else
fi
fi
# Check for Avahi flags
AC_ARG_ENABLE(avahi-browsing,
[AS_HELP_STRING([--disable-avahi-browsing],
[disable avahi browsing of printers])],,
[enable_avahi_browsing=auto])
have_avahi_browsing=no
if test "x$enable_avahi_browsing" != "xno" -a \
"x$have_cups_api_1_6" = "xyes"; then
AC_MSG_CHECKING([avahi-gobject])
if $PKG_CONFIG --exists avahi-gobject ; then
AVAHI_CFLAGS=`$PKG_CONFIG --cflags avahi-gobject`
AC_SUBST(AVAHI_CFLAGS)
AVAHI_LIBS=`$PKG_CONFIG --libs avahi-gobject`
AC_SUBST(AVAHI_LIBS)
have_avahi_gobject=yes
AC_MSG_RESULT([yes])
else
AC_MSG_RESULT([no])
fi
if test "x$have_avahi_gobject" = "xyes" ; then
AC_DEFINE([HAVE_AVAHI_BROWSING], [1], [Define to 1 if avahi-gobject available])
have_avahi_browsing=yes
else
AC_MSG_ERROR([
*** avahi-gobject not found. avahi-gobject is required to build GTK+ with support
*** for avahi browsed printers when using CUPS 1.6.
])
fi
else
if test "x$enable_avahi_browsing" = "xyes"; then
AC_MSG_ERROR([
*** Avahi browsing support requested but CUPS 1.6 not found.
])
fi
fi
# Checks to see if we should compile with PAPI backend for GTK+
#
......@@ -1828,6 +1867,7 @@ echo " Dynamic modules: $build_dynamic_modules"
echo " Included immodules: $included_immodules"
echo " PackageKit support: $build_packagekit"
echo " colord support: $have_colord"
echo " Avahi browsing: $have_avahi_browsing"
echo " Introspection: $found_introspection"
echo " Debugging: $enable_debug"
echo " Documentation: $enable_gtk_doc"
......@@ -12,6 +12,7 @@ AM_CPPFLAGS = \
-I$(top_builddir)/gdk \
$(CUPS_CFLAGS) \
$(COLORD_CFLAGS) \
$(AVAHI_CFLAGS) \
-DGTK_COMPILATION \
-DGTK_PRINT_BACKEND_ENABLE_UNSUPPORTED \
$(GTK_DEP_CFLAGS) \
......@@ -37,6 +38,6 @@ noinst_HEADERS = \
gtkcupsutils.h
libprintbackend_cups_la_LDFLAGS = -avoid-version -module $(no_undefined)
libprintbackend_cups_la_LIBADD = $(LDADDS) $(CUPS_LIBS)
libprintbackend_cups_la_LIBADD = $(LDADDS) $(CUPS_LIBS) $(AVAHI_LIBS)
-include $(top_srcdir)/git.mk
......@@ -87,6 +87,20 @@ static GtkCupsRequestStateFunc get_states[] = {
#define ippSetState(ipp_request, ipp_state) ipp_request->state = ipp_state
#define ippGetString(attr, index, foo) attr->values[index].string.text
#define ippGetCount(attr) attr->num_values
int
ippSetVersion (ipp_t *ipp,
int major,
int minor)
{
if (!ipp || major < 0 || minor < 0)
return 0;
ipp->request.any.version[0] = major;
ipp->request.any.version[1] = minor;
return 1;
}
#endif
static void
......@@ -656,6 +670,13 @@ gtk_cups_request_encode_option (GtkCupsRequest *request,
}
}
void
gtk_cups_request_set_ipp_version (GtkCupsRequest *request,
gint major,
gint minor)
{
ippSetVersion (request->ipp_request, major, minor);
}
static void
_connect (GtkCupsRequest *request)
......
......@@ -178,6 +178,9 @@ gboolean gtk_cups_request_is_done (GtkCupsRequest *
void gtk_cups_request_encode_option (GtkCupsRequest *request,
const gchar *option,
const gchar *value);
void gtk_cups_request_set_ipp_version (GtkCupsRequest *request,
gint major,
gint minor);
gboolean gtk_cups_result_is_error (GtkCupsResult *result);
ipp_t * gtk_cups_result_get_response (GtkCupsResult *result);
GtkCupsErrorType gtk_cups_result_get_error_type (GtkCupsResult *result);
......
......@@ -53,6 +53,12 @@
#include <gtk/gtkprintbackend.h>
#include <gtk/gtkunixprint.h>
#include <gtk/gtkprinter-private.h>
#ifdef HAVE_AVAHI_BROWSING
#include <avahi-gobject/ga-client.h>
#include <avahi-gobject/ga-service-browser.h>
#include <avahi-gobject/ga-service-resolver.h>
#include <avahi-gobject/ga-error.h>
#endif
#include "gtkprintbackendcups.h"
#include "gtkprintercups.h"
......@@ -137,6 +143,13 @@ struct _GtkPrintBackendCups
#ifdef HAVE_COLORD
CdClient *colord_client;
#endif
#ifdef HAVE_AVAHI_BROWSING
gchar *avahi_default_printer;
GList *avahi_resolvers;
GaServiceBrowser *avahi_ipp_browser;
GaServiceBrowser *avahi_ipps_browser;
GaClient *avahi_client;
#endif
};
static GObjectClass *backend_parent_class;
......@@ -203,6 +216,11 @@ void overwrite_and_free (gpointer
static gboolean is_address_local (const gchar *address);
static gboolean request_auth_info (gpointer data);
#ifdef HAVE_AVAHI_BROWSING
static void avahi_data_free (GtkPrintBackendCups *cups_backend);
static void avahi_request_printer_list (GtkPrintBackendCups *cups_backend);
#endif
static void
gtk_print_backend_cups_register_type (GTypeModule *module)
{
......@@ -261,6 +279,15 @@ pb_module_create (void)
#define ippGetName(attr) attr->name
#define ippGetCount(attr) attr->num_values
#define ippGetGroupTag(attr) attr->group_tag
static int
ippGetRange (ipp_attribute_t *attr,
int element,
int *upper)
{
*upper = attr->values[element].range.upper;
return (attr->values[element].range.lower);
}
#endif
/*
* GtkPrintBackendCups
......@@ -584,7 +611,7 @@ gtk_print_backend_cups_print_stream (GtkPrintBackend *print_backend,
GtkPrinterCups *cups_printer;
CupsPrintStreamData *ps;
CupsOptionsData *options_data;
GtkCupsRequest *request;
GtkCupsRequest *request = NULL;
GtkPrintSettings *settings;
const gchar *title;
char printer_absolute_uri[HTTP_MAX_URI];
......@@ -595,23 +622,74 @@ gtk_print_backend_cups_print_stream (GtkPrintBackend *print_backend,
cups_printer = GTK_PRINTER_CUPS (gtk_print_job_get_printer (job));
settings = gtk_print_job_get_settings (job);
request = gtk_cups_request_new_with_username (NULL,
GTK_CUPS_POST,
IPP_PRINT_JOB,
data_io,
NULL,
cups_printer->device_uri,
GTK_PRINT_BACKEND_CUPS (print_backend)->username);
#ifdef HAVE_AVAHI_BROWSING
if (cups_printer->avahi_browsed)
{
http_t *http;
http = httpConnect (cups_printer->hostname, cups_printer->port);
if (http)
{
request = gtk_cups_request_new_with_username (http,
GTK_CUPS_POST,
IPP_PRINT_JOB,
data_io,
cups_printer->hostname,
cups_printer->device_uri,
GTK_PRINT_BACKEND_CUPS (print_backend)->username);
g_snprintf (printer_absolute_uri, HTTP_MAX_URI, cups_printer->printer_uri);
}
else
{
GError *error = NULL;
GTK_NOTE (PRINTING,
g_warning ("CUPS Backend: Error connecting to %s:%d",
cups_printer->hostname,
cups_printer->port));
error = g_error_new (gtk_print_error_quark (),
GTK_CUPS_ERROR_GENERAL,
"Error connecting to %s",
cups_printer->hostname);
gtk_print_job_set_status (job, GTK_PRINT_STATUS_FINISHED_ABORTED);
if (callback)
{
callback (job, user_data, error);
}
g_clear_error (&error);
return;
}
}
else
#endif
{
request = gtk_cups_request_new_with_username (NULL,
GTK_CUPS_POST,
IPP_PRINT_JOB,
data_io,
NULL,
cups_printer->device_uri,
GTK_PRINT_BACKEND_CUPS (print_backend)->username);
httpAssembleURIf (HTTP_URI_CODING_ALL,
printer_absolute_uri,
sizeof (printer_absolute_uri),
"ipp",
NULL,
"localhost",
ippPort (),
"/printers/%s",
gtk_printer_get_name (gtk_print_job_get_printer (job)));
}
httpAssembleURIf (HTTP_URI_CODING_ALL,
printer_absolute_uri,
sizeof (printer_absolute_uri),
"ipp",
NULL,
"localhost",
ippPort (),
"/printers/%s",
gtk_printer_get_name (gtk_print_job_get_printer (job)));
gtk_cups_request_set_ipp_version (request,
cups_printer->ipp_version_major,
cups_printer->ipp_version_minor);
gtk_cups_request_ipp_add_string (request, IPP_TAG_OPERATION,
IPP_TAG_URI, "printer-uri",
......@@ -681,6 +759,14 @@ gtk_print_backend_cups_init (GtkPrintBackendCups *backend_cups)
backend_cups->colord_client = cd_client_new ();
#endif
#ifdef HAVE_AVAHI_BROWSING
backend_cups->avahi_default_printer = NULL;
backend_cups->avahi_resolvers = NULL;
backend_cups->avahi_ipp_browser = NULL;
backend_cups->avahi_ipps_browser = NULL;
backend_cups->avahi_client = NULL;
#endif
cups_get_local_default_printer (backend_cups);
}
......@@ -711,6 +797,10 @@ gtk_print_backend_cups_finalize (GObject *object)
g_object_unref (backend_cups->colord_client);
#endif
#ifdef HAVE_AVAHI_BROWSING
avahi_data_free (backend_cups);
#endif
backend_parent_class->finalize (object);
}
......@@ -1681,6 +1771,30 @@ static const char * printer_strings[] =
N_("There is a problem on printer '%s'.")
};
/* Attributes we're interested in for printers */
static const char * const printer_attrs[] =
{
"printer-name",
"printer-uri-supported",
"member-uris",
"printer-location",
"printer-info",
"printer-state-message",
"printer-state-reasons",
"printer-state",
"queued-job-count",
"printer-is-accepting-jobs",
"job-sheets-supported",
"job-sheets-default",
"printer-type",
"auth-info-required",
"number-up-default",
"ipp-versions-supported",
"multiple-document-handling-supported",
"copies-supported",
"number-up-supported"
};
typedef enum
{
GTK_PRINTER_STATE_LEVEL_NONE = 0,
......@@ -1708,10 +1822,88 @@ typedef struct
gboolean default_printer;
gboolean got_printer_type;
gboolean remote_printer;
#ifdef HAVE_AVAHI_BROWSING
gboolean avahi_printer;
#endif
gchar **auth_info_required;
gint default_number_up;
guchar ipp_version_major;
guchar ipp_version_minor;
gboolean supports_copies;
gboolean supports_collate;
gboolean supports_number_up;
} PrinterSetupInfo;
static void
get_ipp_version (const char *ipp_version_string,
guchar *ipp_version_major,
guchar *ipp_version_minor)
{
gchar **ipp_version_strv;
gchar *endptr;
*ipp_version_major = 1;
*ipp_version_minor = 1;
if (ipp_version_string)
{
ipp_version_strv = g_strsplit (ipp_version_string, ".", 0);
if (ipp_version_strv)
{
if (g_strv_length (ipp_version_strv) == 2)
{
*ipp_version_major = (guchar) g_ascii_strtoull (ipp_version_strv[0], &endptr, 10);
if (endptr == ipp_version_strv[0])
*ipp_version_major = 1;
*ipp_version_minor = (guchar) g_ascii_strtoull (ipp_version_strv[1], &endptr, 10);
if (endptr == ipp_version_strv[1])
*ipp_version_minor = 1;
}
g_strfreev (ipp_version_strv);
}
}
}
static void
get_server_ipp_version (guchar *ipp_version_major,
guchar *ipp_version_minor)
{
*ipp_version_major = 1;
*ipp_version_minor = 1;
if (IPP_VERSION && strlen (IPP_VERSION) == 2)
{
*ipp_version_major = (unsigned char) IPP_VERSION[0];
*ipp_version_minor = (unsigned char) IPP_VERSION[1];
}
}
static gint
ipp_version_cmp (guchar ipp_version_major1,
guchar ipp_version_minor1,
guchar ipp_version_major2,
guchar ipp_version_minor2)
{
if (ipp_version_major1 == ipp_version_major2 &&
ipp_version_minor1 == ipp_version_minor2)
{
return 0;
}
else if (ipp_version_major1 < ipp_version_major2 ||
(ipp_version_major1 == ipp_version_major2 &&
ipp_version_minor1 < ipp_version_minor2))
{
return -1;
}
else
{
return 1;
}
}
static void
cups_printer_handle_attribute (GtkPrintBackendCups *cups_backend,
ipp_attribute_t *attr,
......@@ -1839,6 +2031,63 @@ cups_printer_handle_attribute (GtkPrintBackendCups *cups_backend,
{
info->default_number_up = ippGetInteger (attr, 0);
}
else if (g_strcmp0 (ippGetName (attr), "ipp-versions-supported") == 0)
{
guchar server_ipp_version_major;
guchar server_ipp_version_minor;
guchar ipp_version_major;
guchar ipp_version_minor;
get_server_ipp_version (&server_ipp_version_major,
&server_ipp_version_minor);
for (i = 0; i < ippGetCount (attr); i++)
{
get_ipp_version (ippGetString (attr, i, NULL),
&ipp_version_major,
&ipp_version_minor);
if (ipp_version_cmp (ipp_version_major,
ipp_version_minor,
info->ipp_version_major,
info->ipp_version_minor) > 0 &&
ipp_version_cmp (ipp_version_major,
ipp_version_minor,
server_ipp_version_major,
server_ipp_version_minor) <= 0)
{
info->ipp_version_major = ipp_version_major;
info->ipp_version_minor = ipp_version_minor;
}
}
}
else if (g_strcmp0 (ippGetName (attr), "number-up-supported") == 0)
{
if (ippGetCount (attr) == 6)
{
info->supports_number_up = TRUE;
}
}
else if (g_strcmp0 (ippGetName (attr), "copies-supported") == 0)
{
int upper = 1;
ippGetRange (attr, 0, &upper);
if (upper > 1)
{
info->supports_copies = TRUE;
}
}
else if (g_strcmp0 (ippGetName (attr), "multiple-document-handling-supported") == 0)
{
for (i = 0; i < ippGetCount (attr); i++)
{
if (g_strcmp0 (ippGetString (attr, i, NULL), "separate-documents-collated-copies") == 0)
{
info->supports_collate = TRUE;
}
}
}
else
{
GTK_NOTE (PRINTING,
......@@ -1862,9 +2111,16 @@ cups_create_printer (GtkPrintBackendCups *cups_backend,
char *cups_server; /* CUPS server */
#ifdef HAVE_COLORD
cups_printer = gtk_printer_cups_new (info->printer_name,
backend,
cups_backend->colord_client);
#ifdef HAVE_AVAHI_BROWSING
if (info->avahi_printer)
cups_printer = gtk_printer_cups_new (info->printer_name,
backend,
NULL);
else
#endif
cups_printer = gtk_printer_cups_new (info->printer_name,
backend,
cups_backend->colord_client);
#else
cups_printer = gtk_printer_cups_new (info->printer_name, backend, NULL);
#endif
......@@ -1921,7 +2177,8 @@ cups_create_printer (GtkPrintBackendCups *cups_backend,
cups_printer->default_cover_before = g_strdup (info->default_cover_before);
cups_printer->default_cover_after = g_strdup (info->default_cover_after);
cups_printer->default_number_up = info->default_number_up;
if (info->default_number_up > 0)
cups_printer->default_number_up = info->default_number_up;
cups_printer->hostname = g_strdup (hostname);
cups_printer->port = port;
......@@ -1935,11 +2192,592 @@ cups_create_printer (GtkPrintBackendCups *cups_backend,
strcmp (cups_backend->default_printer, gtk_printer_get_name (printer)) == 0)
gtk_printer_set_is_default (printer, TRUE);
#ifdef HAVE_AVAHI_BROWSING
cups_printer->avahi_browsed = info->avahi_printer;
#endif
gtk_print_backend_add_printer (backend, printer);
return printer;
}
static void
set_printer_icon_name_from_info (GtkPrinter *printer,
PrinterSetupInfo *info)
{
/* Set printer icon according to importance
(none, report, warning, error - report is omitted). */
if (info->reason_level == GTK_PRINTER_STATE_LEVEL_ERROR)
gtk_printer_set_icon_name (printer, "printer-error");
else if (info->reason_level == GTK_PRINTER_STATE_LEVEL_WARNING)
gtk_printer_set_icon_name (printer, "printer-warning");
else if (gtk_printer_is_paused (printer))
gtk_printer_set_icon_name (printer, "printer-paused");
else
gtk_printer_set_icon_name (printer, "printer");
}
static void
set_info_state_message (PrinterSetupInfo *info)
{
gint i;
if (info->state_msg && strlen (info->state_msg) == 0)
{
gchar *tmp_msg2 = NULL;
if (info->is_paused && !info->is_accepting_jobs)
/* Translators: this is a printer status. */
tmp_msg2 = g_strdup ( N_("Paused ; Rejecting Jobs"));
if (info->is_paused && info->is_accepting_jobs)
/* Translators: this is a printer status. */
tmp_msg2 = g_strdup ( N_("Paused"));
if (!info->is_paused && !info->is_accepting_jobs)
/* Translators: this is a printer status. */
tmp_msg2 = g_strdup ( N_("Rejecting Jobs"));
if (tmp_msg2 != NULL)
{
g_free (info->state_msg);
info->state_msg = tmp_msg2;
}
}
/* Set description of the reason and combine it with printer-state-message. */
if (info->reason_msg)
{
gchar *reason_msg_desc = NULL;
gboolean found = FALSE;
for (i = 0; i < G_N_ELEMENTS (printer_messages); i++)
{
if (strncmp (info->reason_msg, printer_messages[i],
strlen (printer_messages[i])) == 0)
{
reason_msg_desc = g_strdup_printf (printer_strings[i],
info->printer_name);
found = TRUE;
break;
}
}
if (!found)
info->reason_level = GTK_PRINTER_STATE_LEVEL_NONE;
if (info->reason_level >= GTK_PRINTER_STATE_LEVEL_WARNING)
{
if (strlen (info->state_msg) == 0)
{
g_free (info->state_msg);
info->state_msg = reason_msg_desc;
reason_msg_desc = NULL;
}
else
{
gchar *tmp_msg = NULL;
tmp_msg = g_strjoin (" ; ", info->state_msg,
reason_msg_desc, NULL);
g_free (info->state_msg);
info->state_msg = tmp_msg;
}
}
g_free (reason_msg_desc);
}
}
static void
set_default_printer (GtkPrintBackendCups *cups_backend,
const gchar *default_printer_name)
{
cups_backend->default_printer = g_strdup (default_printer_name);
cups_backend->got_default_printer = TRUE;
if (cups_backend->default_printer != NULL)
{
GtkPrinter *default_printer = NULL;
default_printer = gtk_print_backend_find_printer (GTK_PRINT_BACKEND (cups_backend),
cups_backend->default_printer);
if (default_printer != NULL)
{
gtk_printer_set_is_default (default_printer, TRUE);
g_signal_emit_by_name (GTK_PRINT_BACKEND (cups_backend),
"printer-status-changed", default_printer);
}
}
}
#ifdef HAVE_AVAHI_BROWSING
typedef struct
{
gchar *name;
gchar *type;
gchar *domain;
gchar *host;
gint port;
} AvahiService;
void
avahi_service_free (AvahiService *service)
{
if (service)
{
g_free (service->name);
g_free (service->type);
g_free (service->domain);
g_free (service->host);
g_free (service);
}
}
static void
avahi_data_free (GtkPrintBackendCups *cups_backend)
{
if (cups_backend)
{
g_clear_pointer (&cups_backend->avahi_default_printer, g_free);
g_list_free_full (cups_backend->avahi_resolvers, g_object_unref);
cups_backend->avahi_resolvers = NULL;
g_clear_object (&cups_backend->avahi_ipp_browser);
g_clear_object (&cups_backend->avahi_ipps_browser);
g_clear_object (&cups_backend->avahi_client);
}
}
static void
cups_request_avahi_printer_info_cb (GtkPrintBackendCups *cups_backend,
GtkCupsResult *result,
gpointer user_data)
{
PrinterSetupInfo *info = g_slice_new0 (PrinterSetupInfo);
GtkPrintBackend *backend = GTK_PRINT_BACKEND (cups_backend);
ipp_attribute_t *attr;
AvahiService *service = (AvahiService *) user_data;
GtkPrinter *printer;
gboolean list_has_changed = FALSE;
gboolean status_changed = FALSE;
ipp_t *response;
gdk_threads_enter ();
GTK_NOTE (PRINTING,
g_print ("CUPS Backend: %s\n", G_STRFUNC));
if (gtk_cups_result_is_error (result))
{
GTK_NOTE (PRINTING,
g_warning ("CUPS Backend: Error getting printer info: %s %d %d",
gtk_cups_result_get_error_string (result),
gtk_cups_result_get_error_type (result),
gtk_cups_result_get_error_code (result)));
goto done;
}
response = gtk_cups_result_get_response (result);
attr = ippFirstAttribute (response);
while (attr && ippGetGroupTag (attr) != IPP_TAG_PRINTER)
attr = ippNextAttribute (response);
if (attr)
{
while (attr && ippGetGroupTag (attr) == IPP_TAG_PRINTER)
{
cups_printer_handle_attribute (cups_backend, attr, info);
attr = ippNextAttribute (response);
}
if (info->printer_name && info->printer_uri)
{
info->avahi_printer = TRUE;
if (info->got_printer_type &&
info->default_printer &&
cups_backend->avahi_default_printer == NULL)
cups_backend->avahi_default_printer = g_strdup (info->printer_name);
set_info_state_message (info);
printer = gtk_print_backend_find_printer (backend, info->printer_name);