pax_global_header00006660000000000000000000000064144625467450014533gustar00rootroot0000000000000052 comment=b2c10dd70c521a2e3aeb71f79f90f0599263d440 cpdb-backend-cups-2.0b5/000077500000000000000000000000001446254674500150705ustar00rootroot00000000000000cpdb-backend-cups-2.0b5/.gitignore000066400000000000000000000005331446254674500170610ustar00rootroot00000000000000*~ */*~ *.o *.pdf .vscode/ .gdb_history gmon.out .gitignore aclocal.m4 aclocal.* autom4te.cache/ build-aux/ configure config.guess config.sub config.log config.status Makefile Makefile.in src/.deps src/cups src/cups-notifier.* src/run-tests.sh.log src/run-tests.sh.trs src/test-suite.log data/org.openprinting.Backend.CUPS.service tmp/ */Makefile cpdb-backend-cups-2.0b5/CHANGES.md000066400000000000000000000100121446254674500164540ustar00rootroot00000000000000# CHANGES - Common Print Dialog Backends - CUPS Backend - v2.0b5 - 2023-08-02 ## CHANGES IN V2.0b5 (2nd August 2023) - Remove CPDB backend info file The frontend libraries now use only the D-Bus to find available backends. This makes sure that everything works also if the frontend and/or any of the backends are installed via sanboxed packaging (like Snap for example) where the components cannot read each other's file systems (PR #26). - `get_all_media()`: Do not crash on custom page size range entries The media-col-database IPP attribute contains one entry for each valid combination of page size dimensions, margins, and in some cases also media source and media type. Entries for custom page sizes contain ranges instead of single values. `get_all_media()` crashed on these. Now we let the function simply skip them. - Build system: Removed unnecessary lines in `tools/Makefile.am` Removed the `TESTdir` and `TEST_SCRIPTS` entries in `tools/Makefile.am`. They are not needed and let `make install` try to install `run-tests.sh` in the source directory, where it already is, causing an error. ## CHANGES IN V2.0b4 (21th March 2023) - Added test script for `make test`/`make check` The script `src/run-tests.sh` starts a private session D-Bus via `dbus-run-session` and therein an own copy of CUPS. It uses the CPDB CUPS backend with this CUPS and tests it using the `cpdb-text-frontend` of cpdb-libs, performing several test tasks on the backend. ## CHANGES IN V2.0b3 (20th February 2023) - Add handler for `GetAllTranslations` method and Bug fixes (PR #22) * Fixed bug when backend finds zero printers * Add handler for `GetAllTranslations` method * `get_printer_translations()` fetches translations for all printer strings. * Removed `get_human_readable_option_name()` and `get_human_readable_choice_name()` functions. ## CHANGES IN V2.0b2 (13th February 2023) - Return printer list upon activation and subscribe to cups for updates (PR #20) * Return printer list synchronously upon activation * Subscribe to CUPS notifications for printer updates * Automatically update frontends about new printers found, old printers lost, or printer state changed - Added the support of cpdb-libs for translations * Using general message string catalogs from CUPS and also message string catalogs from individual print queues/IPP printers. * Message catalog management done by libcupsfilters 2.x, via the `cfCatalog...()` API functions (`catalog.h`). - Option group support - Added `billing-info` option (PR #19) - Log messages handled by frontend - Use pkg-config variables instead of hardcoded paths (PR #17) - Build system: Let "make dist" also create .tar.bz2 and .tar.xz ## CHANGES IN V2.0b1 (12th December 2022) - Added function to query for human readable names of options/choices With the added functionality of cpdb-libs to poll human-readable names for options/attributes and their choices this commit adds a simple function to provide human-readable strings for the user-settable printer IPP attributes of CUPS queues. - Added support for common CUPS/cups-filters options Options like number-up, page-set, output-order, ... Available for all CUPS queues, not specific to particular printer. - Added get_media_size() function - Support for media sizes to have multiple margin variants (like standard and borderless) - Do not send media-col attribute to frontend as user-settable option - Adapted to renamed API functions and data types of cpdb-libs 2.x - Updated signal names to match those emitted from CPDB frontend - Made "make dist" generate a complete source tarball - Fixed some potential crasher bugs following warnings about incompatible pointer types. - Updated README.md + On update the old version of the backend needs to get killed + Common Print Dialog -> Common Print Dialog Backends + Requires cpdb-libs 2.0.0 or newer + Updated instructions for running the backend. + Added link to Gaurav Guleria's GSoC work, minor fixes + Use third person. cpdb-backend-cups-2.0b5/COPYING000066400000000000000000000026741446254674500161340ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: cpdb-backend-cups Upstream-Contact: OpenPrinting Source: https://github.com/OpenPrinting/cpdb-backend-cups/releases Files: * Copyright: 2017, Nilanjana Lodh 2017-2022, Till Kamppeter 2020, Priydarshi Singh 2022, Gaurav Guleria License: MIT License: MIT The MIT License . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. . THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cpdb-backend-cups-2.0b5/LICENSE.md000066400000000000000000000022341446254674500164750ustar00rootroot00000000000000MIT License Copyright (c) 2017 Nilanjana Lodh Copyright (c) 2017-2022 Till Kamppeter Copyright (c) 2020 Priydarshi Singh Copyright (c) 2022 Gaurav Guleria Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cpdb-backend-cups-2.0b5/Makefile.am000066400000000000000000000001761446254674500171300ustar00rootroot00000000000000AUTOMAKE_OPTIONS = foreign SUBDIRS = src data EXTRA_DIST = \ README.md \ autogen.sh \ LICENSE.md \ COPYING \ CHANGES.md cpdb-backend-cups-2.0b5/README.md000066400000000000000000000036751446254674500163620ustar00rootroot00000000000000# CUPS Common Print Dialog Backend This repository hosts the code for the CUPS **C**ommon **P**rint **D**ialog **B**ackend. This backend manages and provides information about CUPS and IPP printing destinations to the printing dialog. ## Background The [Common Print Dialog Backends](https://openprinting.github.io/achievements/#common-print-dialog-backends) project aims to move the responsability on the part of the print dialog which communicates with the print system away from the GUI toolkit/app developers to the print system's developers and also to bring all print technologies available to the user (CUPS, cloud printing services, ...) into all application's print dialogs. ## Dependencies - [cpdb-libs](https://github.com/OpenPrinting/cpdb-libs): Version >= 2.0.0 (or GIT Master) - [CUPS](https://github.com/OpenPrinting/cups): Version >= 2.2 `sudo apt install cups libcups2-dev` - GLIB 2.0: `sudo apt install libglib2.0-dev` ## Build and installation ``` $ ./autogen.sh $ ./configure $ make $ sudo make install ``` If you are updating from an older version, please kill the old version of the backend: ``` $ sudo killall cups ``` This way the next time when a print dialog is opened the new version of the backend is started. ## Following the development and updating The current source code you find on the [OpenPrinting GitHub](https://github.com/OpenPrinting/cpdb-backend-cups). ## Running The backend is auto-activated when a frontend (like a CPDB-supporting print dialog or the example frontend `demo/print_frontend` of cpdb-libs) is started, so there is no need to run it explicitly. However, if you wish to see the debug statements in the backend code, you can run `/usr/local/lib/print-backends/cups` ## More Info - [Nilanjana Lodh's Google Summer of Code 2017 Final Report](https://nilanjanalodh.github.io/common-print-dialog-gsoc17/) - [Gaurav Guleria's Google Summer of Code 2022 Final Report](https://github.com/TinyTrebuchet/gsoc22/) cpdb-backend-cups-2.0b5/autogen.sh000077500000000000000000000001261446254674500170700ustar00rootroot00000000000000#! /bin/sh mkdir -p build-aux \ && aclocal \ && automake --add-missing \ && autoconf cpdb-backend-cups-2.0b5/configure.ac000066400000000000000000000030471446254674500173620ustar00rootroot00000000000000# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ([2.69]) AC_INIT([cpdb-backend-cups], [2.0b5], [https://github.com/OpenPrinting/cpdb-backend-cups/issues/], [cpdb-backend-cups], [https://github.com/OpenPrinting/cpdb-backend-cups]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_SRCDIR([src/print_backend_cups.c]) AM_INIT_AUTOMAKE([-Wall dist-xz dist-bzip2 foreign]) : ${CFLAGS=""} # Check for a C compiler AC_PROG_CC # Checks for backend library PKG_CHECK_MODULES([CPDB],[cpdb >= 2]) PKG_CHECK_MODULES([LIBCUPSFILTERS], [libcupsfilters >= 2]) PKG_CHECK_MODULES([GIO],[gio-2.0]) PKG_CHECK_MODULES([GIOUNIX],[gio-unix-2.0]) PKG_CHECK_MODULES([GLIB],[glib-2.0]) # Checks for header files. AC_CHECK_HEADERS([stdlib.h string.h cups/cups.h cupsfilters/catalog.h]) # Checks for typedefs, structures, and compiler characteristics. AC_TYPE_SIZE_T # Checks for library functions AC_FUNC_MALLOC # The Dbus service directory AC_MSG_CHECKING([dbus service file path]) PKG_CHECK_VAR([DBUS_SERVICES_DIR], [dbus-1], [session_bus_services_dir], AC_MSG_RESULT($DBUS_SERVICES_DIR), AC_MSG_FAILURE([cannot locate session_bus_services_dir]) ) # The directory for the backend executables AC_MSG_CHECKING([CPDB backend executables path]) PKG_CHECK_VAR([CPDB_BACKEND_DIR], [cpdb], [cpdb_backend_exec_dir], AC_MSG_RESULT($CPDB_BACKEND_DIR), AC_MSG_FAILURE([cannot locate cpdb_backend_exec_dir]) ) AC_CONFIG_FILES([Makefile src/Makefile data/Makefile]) AC_OUTPUT cpdb-backend-cups-2.0b5/data/000077500000000000000000000000001446254674500160015ustar00rootroot00000000000000cpdb-backend-cups-2.0b5/data/Makefile.am000066400000000000000000000006261446254674500200410ustar00rootroot00000000000000EXTRA_DIST = \ org.cups.cupsd.Notifier.xml \ org.openprinting.Backend.CUPS.service.in # Dbus service file servicedir = $(DBUS_SERVICES_DIR) service_in_files = org.openprinting.Backend.CUPS.service.in service_DATA = $(service_in_files:.service.in=.service) $(service_DATA): $(service_in_files) Makefile @sed -e "s|\@cpdb_backend_dir\@|$(CPDB_BACKEND_DIR)|" $<> $@ clean-local: rm -rf $(service_DATA) cpdb-backend-cups-2.0b5/data/org.cups.cupsd.Notifier.xml000066400000000000000000000127731446254674500231700ustar00rootroot00000000000000 cpdb-backend-cups-2.0b5/data/org.openprinting.Backend.CUPS.service.in000066400000000000000000000001201446254674500253620ustar00rootroot00000000000000[D-BUS Service] Name=org.openprinting.Backend.CUPS Exec=@cpdb_backend_dir@/cups cpdb-backend-cups-2.0b5/src/000077500000000000000000000000001446254674500156575ustar00rootroot00000000000000cpdb-backend-cups-2.0b5/src/Makefile.am000066400000000000000000000021321446254674500177110ustar00rootroot00000000000000# cups-notifier cups_notifier_sources = \ cups-notifier.c \ cups-notifier.h $(cups_notifier_sources): ../data/org.cups.cupsd.Notifier.xml gdbus-codegen \ --interface-prefix org.cups.cupsd \ --c-namespace Cups \ --generate-c-code cups-notifier \ ../data/org.cups.cupsd.Notifier.xml BUILT_SOURCES = $(cups_notifier_sources) CLEANFILES = $(BUILT_SOURCES) backenddir = $(CPDB_BACKEND_DIR) backend_PROGRAMS = cups cups_SOURCES = \ print_backend_cups.c \ backend_helper.c backend_helper.h \ cups-notifier.c cups-notifier.h cups_CPPFLAGS = $(CPDB_CFLAGS) cups_CPPFLAGS += $(LIBCUPSFILTERS_CFLAGS) cups_CPPFLAGS += $(GLIB_CFLAGS) cups_CPPFLAGS += $(GIO_CFLAGS) cups_CPPFLAGS += $(GIOUNIX_CFLAGS) cups_LDADD = -lcups -lpthread -lm -lcrypt cups_LDADD += $(CPDB_LIBS) cups_LDADD += $(LIBCUPSFILTERS_LIBS) cups_LDADD += $(GLIB_LIBS) cups_LDADD += $(GIO_LIBS) cups_LDADD += $(GIOUNIX_LIBS) # ================================ # Tests ("make test"/"make check") # ================================ TESTS = \ run-tests.sh EXTRA_DIST = \ run-tests.sh \ test.convs \ testpdf.ppd cpdb-backend-cups-2.0b5/src/backend_helper.c000066400000000000000000002131471446254674500207610ustar00rootroot00000000000000#include "backend_helper.h" #define _CUPS_NO_DEPRECATED 1 static http_t *system_conn = NULL; static unsigned int HttpLocalTimeout = 5; Mappings *map; /*****************BackendObj********************************/ BackendObj *get_new_BackendObj() { map = get_new_Mappings(); BackendObj *b = (BackendObj *)(malloc(sizeof(BackendObj))); b->dbus_connection = NULL; b->dialogs = g_hash_table_new_full(g_str_hash, g_str_equal, (GDestroyNotify)free_string, (GDestroyNotify)free_Dialog); b->num_frontends = 0; b->obj_path = NULL; b->default_printer = NULL; return b; } /** Don't free the returned value; it is owned by BackendObj */ char *get_default_printer(BackendObj *b) { /** If it was previously querie, don't query again */ if (b->default_printer) { return b->default_printer; } /**first query to see if the user default printer is set**/ int num_dests; cups_dest_t *dests; num_dests = cupsGetDests2(CUPS_HTTP_DEFAULT, &dests); /** Get the list of all destinations */ cups_dest_t *dest = cupsGetDest(NULL, NULL, num_dests, dests); /** Get the default one */ if (dest) { /** Return the user default printer */ char *def = cpdbGetStringCopy(dest->name); cupsFreeDests(num_dests, dests); b->default_printer = def; return def; } cupsFreeDests(num_dests, dests); /** Then query the system default printer **/ ipp_t *request = ippNewRequest(IPP_OP_CUPS_GET_DEFAULT); ipp_t *response; ipp_attribute_t *attr; if ((response = cupsDoRequest(CUPS_HTTP_DEFAULT, request, "/")) != NULL) { if ((attr = ippFindAttribute(response, "printer-name", IPP_TAG_NAME)) != NULL) { b->default_printer = cpdbGetStringCopy(ippGetString(attr, 0, NULL)); ippDelete(response); return b->default_printer; } } ippDelete(response); b->default_printer = cpdbGetStringCopy("NA"); return b->default_printer; } void connect_to_dbus(BackendObj *b, char *obj_path) { b->obj_path = obj_path; GError *error = NULL; g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(b->skeleton), b->dbus_connection, obj_path, &error); if (error) { MSG_LOG("Error connecting CUPS Backend to D-Bus.\n", ERR); } } void add_frontend(BackendObj *b, const char *dialog_name) { Dialog *d = get_new_Dialog(); g_hash_table_insert(b->dialogs, cpdbGetStringCopy(dialog_name), d); b->num_frontends++; } void remove_frontend(BackendObj *b, const char *dialog_name) { Dialog *d = (Dialog *)(g_hash_table_lookup(b->dialogs, dialog_name)); if (d) { g_hash_table_remove(b->dialogs, dialog_name); b->num_frontends--; } g_message("Removed Frontend entry for %s", dialog_name); } gboolean no_frontends(BackendObj *b) { if ((b->num_frontends) == 0) return TRUE; return FALSE; } Dialog *find_dialog(BackendObj *b, const char *dialog_name) { Dialog *d = (Dialog *)(g_hash_table_lookup(b->dialogs, dialog_name)); return d; } int *get_dialog_cancel(BackendObj *b, const char *dialog_name) { Dialog *d = (Dialog *)(g_hash_table_lookup(b->dialogs, dialog_name)); return &d->cancel; } void set_dialog_cancel(BackendObj *b, const char *dialog_name) { int *x = get_dialog_cancel(b, dialog_name); *x = 1; } void reset_dialog_cancel(BackendObj *b, const char *dialog_name) { int *x = get_dialog_cancel(b, dialog_name); *x = 0; } void set_hide_remote_printers(BackendObj *b, const char *dialog_name) { Dialog *d = (Dialog *)(g_hash_table_lookup(b->dialogs, dialog_name)); d->hide_remote = TRUE; } void unset_hide_remote_printers(BackendObj *b, const char *dialog_name) { Dialog *d = (Dialog *)(g_hash_table_lookup(b->dialogs, dialog_name)); d->hide_remote = FALSE; } void set_hide_temp_printers(BackendObj *b, const char *dialog_name) { Dialog *d = (Dialog *)(g_hash_table_lookup(b->dialogs, dialog_name)); d->hide_temp = TRUE; } void unset_hide_temp_printers(BackendObj *b, const char *dialog_name) { Dialog *d = (Dialog *)(g_hash_table_lookup(b->dialogs, dialog_name)); d->hide_temp = FALSE; } static int http_timeout_cb(http_t *http, void *user_data) { logdebug("HTTP timeout! (consider increasing HttpLocalTimeout/HttpRemoteTimeout value)\n"); return (0); } /* Connect to the system's CUPS daemon and also tell the libcups functions to use the system's CUPS */ static http_t * http_connect_system(void) { const char *server = cupsServer(); int port = ippPort(); if (!system_conn) { if (server[0] == '/') logdebug("Creating http connection to CUPS daemon via domain socket: %s\n", server); else logdebug("Creating http connection to CUPS daemon: %s:%d\n", server, port); system_conn = httpConnect2(server, port, NULL, AF_UNSPEC, cupsEncryption(), 1, 3000, NULL); } if (system_conn) { httpSetTimeout(system_conn, HttpLocalTimeout, http_timeout_cb, NULL); } else { if (server[0] == '/') logwarn("Failed creating http connection to CUPS daemon via domain socket: %s\n", server); else logwarn("Failed creating http connection to CUPS daemon: %s:%d\n", server, port); } return (system_conn); } /* Close connection to system's CUPS */ static void http_close_system(void) { logdebug("Closing connection to system's CUPS daemon.\n"); if (system_conn) { httpClose(system_conn); system_conn = NULL; } } /* Create a subscription for D-Bus notifications on the system's CUPS. This makes the CUPS daemon fire up a D-Bus notifier process. */ int create_subscription () { ipp_t *req; ipp_t *resp; ipp_attribute_t *attr; int id = 0; http_t *conn = NULL; conn = http_connect_system(); if (conn == NULL) { logwarn("Cannot connect to local CUPS to subscribe to notifications.\n"); return (0); } req = ippNewRequest(IPP_CREATE_PRINTER_SUBSCRIPTION); ippAddString(req, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, "/"); ippAddString(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD, "notify-events", NULL, "all"); ippAddString(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_URI, "notify-recipient-uri", NULL, "dbus://"); ippAddInteger(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, "notify-lease-duration", NOTIFY_LEASE_DURATION); resp = cupsDoRequest(conn, req, "/"); if (!resp || cupsLastError() != IPP_STATUS_OK) { logwarn("Error subscribing to CUPS notifications: %s\n", cupsLastErrorString ()); return (0); } attr = ippFindAttribute(resp, "notify-subscription-id", IPP_TAG_INTEGER); if (attr) { id = ippGetInteger(attr, 0); } else { logwarn("ipp-create-printer-subscription response doesn't contain" "subscription id.\n"); } ippDelete(resp); return (id); } /* Renew the D-Bus notification subscription, telling to CUPS that we are still there and it should not let the notifier time out. */ gboolean renew_subscription (int id) { ipp_t *req; ipp_t *resp; http_t *http = NULL; http = http_connect_system(); if (http == NULL) { logwarn("Cannot connect to system's CUPS to renew subscriptions!\n"); return FALSE; } req = ippNewRequest(IPP_RENEW_SUBSCRIPTION); ippAddInteger(req, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "notify-subscription-id", id); ippAddString(req, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, "/"); ippAddString(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_URI, "notify-recipient-uri", NULL, "dbus://"); ippAddInteger(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, "notify-lease-duration", NOTIFY_LEASE_DURATION); resp = cupsDoRequest(http, req, "/"); if (!resp || cupsLastError() != IPP_STATUS_OK) { logwarn("Error renewing CUPS subscription %d: %s\n", id, cupsLastErrorString()); http_close_system(); return FALSE; } ippDelete(resp); http_close_system(); return TRUE; } /* Function which is called as a timeout event handler to let the renewal of the D-Bus subscription be done to the right time. */ gboolean renew_subscription_timeout (gpointer userdata) { int *subscription_id = userdata; logdebug("renew_subscription_timeout() in THREAD %ld\n", pthread_self()); if (*subscription_id <= 0 || !renew_subscription(*subscription_id)) *subscription_id = create_subscription(); return TRUE; } /* Cancel the D-Bus notifier subscription, so that CUPS can terminate its notifier when we shut down. */ void cancel_subscription (int id) { ipp_t *req; ipp_t *resp; http_t *http = NULL; if (id <= 0) return; http = http_connect_system(); if (http == NULL) { logwarn("Cannot connect to system's CUPS to cancel subscriptions.\n"); return; } req = ippNewRequest(IPP_CANCEL_SUBSCRIPTION); ippAddString(req, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, "/"); ippAddInteger(req, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "notify-subscription-id", id); resp = cupsDoRequest(http, req, "/"); if (!resp || cupsLastError() != IPP_STATUS_OK) { logwarn("Error canceling subscription to CUPS notifications: %s\n", cupsLastErrorString()); http_close_system(); return; } ippDelete(resp); http_close_system(); } gboolean dialog_contains_printer(BackendObj *b, const char *dialog_name, const char *printer_name) { Dialog *d = g_hash_table_lookup(b->dialogs, dialog_name); if (d == NULL || d->printers == NULL) { char *msg = malloc(sizeof(char) * (strlen(dialog_name) + 50)); sprintf(msg, "Can't retrieve printers for dialog %s.\n", dialog_name); MSG_LOG(msg, ERR); free(msg); return FALSE; } if (g_hash_table_contains(d->printers, printer_name)) return TRUE; return FALSE; } PrinterCUPS *add_printer_to_dialog(BackendObj *b, const char *dialog_name, const cups_dest_t *dest) { char *printer_name = cpdbGetStringCopy(dest->name); Dialog *d = (Dialog *)g_hash_table_lookup(b->dialogs, dialog_name); if (d == NULL) { char *msg = malloc(sizeof(char) * (strlen(dialog_name) + 50)); sprintf(msg, "Invalid dialog name %s.\n", dialog_name); MSG_LOG(msg, ERR); free(msg); return NULL; } PrinterCUPS *p = get_new_PrinterCUPS(dest); g_hash_table_insert(d->printers, printer_name, p); return p; } void remove_printer_from_dialog(BackendObj *b, const char *dialog_name, const char *printer_name) { Dialog *d = (Dialog *)g_hash_table_lookup(b->dialogs, dialog_name); if (d == NULL) { char *msg = malloc(sizeof(char) * (strlen(printer_name) + 50)); sprintf(msg, "Unable to remove printer %s.\n", printer_name); MSG_LOG(msg, WARN); free(msg); return; } g_hash_table_remove(d->printers, printer_name); } void send_printer_added_signal(BackendObj *b, const char *dialog_name, cups_dest_t *dest) { if (dest == NULL) { MSG_LOG("Failed to send printer added signal.\n", ERR); exit(EXIT_FAILURE); } char *printer_name = cpdbGetStringCopy(dest->name); GVariant *gv = g_variant_new(CPDB_PRINTER_ADDED_ARGS, printer_name, //id printer_name, //name cups_retrieve_string(dest, "printer-info"), //info cups_retrieve_string(dest, "printer-location"), //location cups_retrieve_string(dest, "printer-make-and-model"), cups_is_accepting_jobs(dest), cups_printer_state(dest), "CUPS"); GError *error = NULL; g_dbus_connection_emit_signal(b->dbus_connection, dialog_name, b->obj_path, "org.openprinting.PrintBackend", CPDB_SIGNAL_PRINTER_ADDED, gv, &error); g_assert_no_error(error); } void send_printer_removed_signal(BackendObj *b, const char *dialog_name, const char *printer_name) { GError *error = NULL; g_dbus_connection_emit_signal(b->dbus_connection, dialog_name, b->obj_path, "org.openprinting.PrintBackend", CPDB_SIGNAL_PRINTER_REMOVED, g_variant_new("(ss)", printer_name, "CUPS"), &error); g_assert_no_error(error); } void send_printer_state_changed_signal(BackendObj *b, const char *dialog_name, const char *printer_name, const char *printer_state, gboolean printer_is_accepting_jobs) { GError *error = NULL; g_dbus_connection_emit_signal(b->dbus_connection, dialog_name, b->obj_path, "org.openprinting.PrintBackend", CPDB_SIGNAL_PRINTER_REMOVED, g_variant_new("(ssbs)", printer_name, printer_state, printer_is_accepting_jobs, "CUPS"), &error); g_assert_no_error(error); } void notify_removed_printers(BackendObj *b, const char *dialog_name, GHashTable *new_table) { Dialog *d = (Dialog *)g_hash_table_lookup(b->dialogs, dialog_name); GHashTable *prev = d->printers; GList *prevlist = g_hash_table_get_keys(prev); printf("Notifying removed printers.\n"); gpointer printer_name = NULL; while (prevlist) { printer_name = (char *)(prevlist->data); if (!g_hash_table_contains(new_table, (gchar *)printer_name)) { g_message("Printer %s removed\n", (char *)printer_name); send_printer_removed_signal(b, dialog_name, (char *)printer_name); remove_printer_from_dialog(b, dialog_name, (char *)printer_name); } prevlist = prevlist->next; } } void notify_added_printers(BackendObj *b, const char *dialog_name, GHashTable *new_table) { GHashTableIter iter; Dialog *d = (Dialog *)g_hash_table_lookup(b->dialogs, dialog_name); GHashTable *prev = d->printers; printf("Notifying added printers.\n"); gpointer printer_name; gpointer value; cups_dest_t *dest = NULL; g_hash_table_iter_init(&iter, new_table); while (g_hash_table_iter_next(&iter, &printer_name, &value)) { if (!g_hash_table_contains(prev, (gchar *)printer_name)) { g_message("Printer %s added\n", (char *)printer_name); dest = (cups_dest_t *)value; send_printer_added_signal(b, dialog_name, dest); add_printer_to_dialog(b, dialog_name, dest); } } } gboolean get_hide_remote(BackendObj *b, char *dialog_name) { Dialog *d = (Dialog *)g_hash_table_lookup(b->dialogs, dialog_name); return d->hide_remote; } gboolean get_hide_temp(BackendObj *b, char *dialog_name) { Dialog *d = (Dialog *)g_hash_table_lookup(b->dialogs, dialog_name); return d->hide_temp; } void refresh_printer_list(BackendObj *b, char *dialog_name) { GHashTable *new_printers; new_printers = cups_get_printers(get_hide_temp(b, dialog_name), get_hide_remote(b, dialog_name)); notify_removed_printers(b, dialog_name, new_printers); notify_added_printers(b, dialog_name, new_printers); } GHashTable *get_dialog_printers(BackendObj *b, const char *dialog_name) { Dialog *d = (Dialog *)g_hash_table_lookup(b->dialogs, dialog_name); if (d == NULL) { MSG_LOG("Invalid dialog name.\n", ERR); return NULL; } return d->printers; } PrinterCUPS *get_printer_by_name(BackendObj *b, const char *dialog_name, const char *printer_name) { GHashTable *printers = get_dialog_printers(b, dialog_name); g_assert_nonnull(printers); PrinterCUPS *p = (g_hash_table_lookup(printers, printer_name)); if (p == NULL) { printf("Printer '%s' does not exist for the dialog %s.\n", printer_name, dialog_name); exit(EXIT_FAILURE); } return p; } cups_dest_t *get_dest_by_name(BackendObj *b, const char *dialog_name, const char *printer_name) { GHashTable *printers = get_dialog_printers(b, dialog_name); g_assert_nonnull(printers); PrinterCUPS *p = (g_hash_table_lookup(printers, printer_name)); if (p == NULL) { printf("Printer '%s' does not exist for the dialog %s.\n", printer_name, dialog_name); } return p->dest; } GVariant *get_all_jobs(BackendObj *b, const char *dialog_name, int *num_jobs, gboolean active_only) { int CUPS_JOB_FLAG; if (active_only) CUPS_JOB_FLAG = CUPS_WHICHJOBS_ACTIVE; else CUPS_JOB_FLAG = CUPS_WHICHJOBS_ALL; GHashTable *printers = get_dialog_printers(b, dialog_name); GVariantBuilder *builder; GVariant *variant; builder = g_variant_builder_new(G_VARIANT_TYPE(CPDB_JOB_ARRAY_ARGS)); GHashTableIter iter; gpointer key, value; g_hash_table_iter_init(&iter, printers); int ncurr = 0; int n = 0; int num_printers = g_hash_table_size(printers); cups_job_t **jobs = g_new(cups_job_t *, num_printers); int i_printer = 0; while (g_hash_table_iter_next(&iter, &key, &value)) { /** iterate over all the printers of this dialog **/ PrinterCUPS *p = (PrinterCUPS *)value; ensure_printer_connection(p); printf(" .. %s ..", p->name); /**This is NOT reporting jobs for ipp printers : Probably a bug in cupsGetJobs2:(( **/ ncurr = cupsGetJobs2(p->http, &(jobs[i_printer]), p->name, 0, CUPS_JOB_FLAG); printf("%d\n", ncurr); n += ncurr; for (int i = 0; i < ncurr; i++) { printf("i = %d\n", i); printf("%d %s\n", jobs[i_printer][i].id, jobs[i_printer][i].title); g_variant_builder_add_value(builder, pack_cups_job(jobs[i_printer][i])); } cupsFreeJobs(ncurr, jobs[i_printer]); i_printer++; } free(jobs); *num_jobs = n; variant = g_variant_new(CPDB_JOB_ARRAY_ARGS, builder); return variant; } /***************************PrinterObj********************************/ PrinterCUPS *get_new_PrinterCUPS(const cups_dest_t *dest) { PrinterCUPS *p = (PrinterCUPS *)(malloc(sizeof(PrinterCUPS))); /** Make a copy of dest, because there are no guarantees * whether dest will always exist or if it will be freed**/ cups_dest_t *dest_copy = NULL; cupsCopyDest((cups_dest_t *)dest, 0, &dest_copy); if (dest_copy == NULL) { MSG_LOG("Error creating PrinterCUPS", WARN); return NULL; } p->dest = dest_copy; p->name = dest_copy->name; p->http = NULL; p->dinfo = NULL; return p; } void free_PrinterCUPS(PrinterCUPS *p) { printf("Freeing printerCUPS \n"); cupsFreeDests(1, p->dest); if (p->dinfo) { cupsFreeDestInfo(p->dinfo); } } gboolean ensure_printer_connection(PrinterCUPS *p) { if (p->http) return TRUE; int temp = FALSE; if (cups_is_temporary(p->dest)) temp = TRUE; p->http = cupsConnectDest(p->dest, CUPS_DEST_FLAGS_NONE, 300, NULL, NULL, 0, NULL, NULL); if (p->http == NULL) return FALSE; // update dest after temporary CUPS queue has been created if (temp) { cups_dest_t *new_dest = cupsGetNamedDest(p->http, p->name, NULL); cupsFreeDests(1, p->dest); p->dest = new_dest; } p->dinfo = cupsCopyDestInfo(p->http, p->dest); if (p->dinfo == NULL) return FALSE; return TRUE; } int get_supported(PrinterCUPS *p, char ***supported_values, const char *option_name) { char **values; ensure_printer_connection(p); ipp_attribute_t *attrs = cupsFindDestSupported(p->http, p->dest, p->dinfo, option_name); int i, count = ippGetCount(attrs); if (!count) { *supported_values = NULL; return 0; } values = malloc(sizeof(char *) * count); for (i = 0; i < count; i++) { values[i] = extract_ipp_attribute(attrs, i, option_name); } *supported_values = values; return count; } char *get_orientation_default(PrinterCUPS *p) { const char *def_value = cupsGetOption(CUPS_ORIENTATION, p->dest->num_options, p->dest->options); if (def_value) { switch (def_value[0]) { case '0': return cpdbGetStringCopy("automatic-rotation"); default: return cpdbGetStringCopy(ippEnumString(CUPS_ORIENTATION, atoi(def_value))); } } ensure_printer_connection(p); ipp_attribute_t *attr = NULL; attr = cupsFindDestDefault(p->http, p->dest, p->dinfo, CUPS_ORIENTATION); if (!attr) return cpdbGetStringCopy("NA"); const char *str = ippEnumString(CUPS_ORIENTATION, ippGetInteger(attr, 0)); if (strcmp("0", str) == 0) str = "automatic-rotation"; return cpdbGetStringCopy(str); } int get_job_creation_attributes(PrinterCUPS *p, char ***values) { return get_supported(p, values, "job-creation-attributes"); } char *get_default(PrinterCUPS *p, char *option_name) { /** first take care of special cases**/ if (strcmp(option_name, CUPS_ORIENTATION) == 0) return get_orientation_default(p); /** Generic cases next **/ ensure_printer_connection(p); ipp_attribute_t *def_attr = cupsFindDestDefault(p->http, p->dest, p->dinfo, option_name); const char *def_value = cupsGetOption(option_name, p->dest->num_options, p->dest->options); /** First check the option is already there in p->dest->options **/ if (def_value) { if (def_attr && (ippGetValueTag(def_attr) == IPP_TAG_ENUM)) return cpdbGetStringCopy(ippEnumString(option_name, atoi(def_value))); return cpdbGetStringCopy(def_value); } if (def_attr) { return extract_ipp_attribute(def_attr, 0, option_name); } return cpdbGetStringCopy("NA"); } /**************Option************************************/ Option *get_NA_option() { Option *o = (Option *)malloc(sizeof(Option)); o->option_name = "NA"; o->default_value = "NA"; o->num_supported = 0; o->supported_values = cpdbNewCStringArray(1); o->supported_values[0] = "bub"; return o; } void print_option(const Option *opt) { g_message("%s", opt->option_name); int i; for (i = 0; i < opt->num_supported; i++) { printf(" %s\n", opt->supported_values[i]); } printf("****DEFAULT: %s\n", opt->default_value); } void free_options(int count, Option *opts) { int i, j; /**Looping variables */ for (i = 0; i < count; i++) { free(opts[i].option_name); for (j = 0; j < opts[i].num_supported; j++) { free(opts[i].supported_values[j]); } free(opts[i].supported_values); free(opts[i].default_value); } free(opts); } void unpack_option_array(GVariant *var, int num_options, Option **options) { Option *opt = (Option *)(malloc(sizeof(Option) * num_options)); int i, j; char *str; GVariantIter *iter; GVariantIter *array_iter; char *name, *default_val; int num_sup; g_variant_get(var, "a(ssia(s))", &iter); for (i = 0; i < num_options; i++) { //printf("i = %d\n", i); g_variant_iter_loop(iter, "(ssia(s))", &name, &default_val, &num_sup, &array_iter); opt[i].option_name = cpdbGetStringCopy(name); opt[i].default_value = cpdbGetStringCopy(default_val); opt[i].num_supported = num_sup; opt[i].supported_values = cpdbNewCStringArray(num_sup); for (j = 0; j < num_sup; j++) { g_variant_iter_loop(array_iter, "(s)", &str); opt[i].supported_values[j] = cpdbGetStringCopy(str); //mem } print_option(&opt[i]); } *options = opt; } GVariant *pack_option(const Option *opt) { char *group_name = cpdbGetGroup(opt->option_name); GVariant **t = g_new(GVariant *, 5); t[0] = g_variant_new_string(opt->option_name); t[1] = g_variant_new_string(group_name); t[2] = g_variant_new_string(opt->default_value); t[3] = g_variant_new_int32(opt->num_supported); t[4] = cpdbPackStringArray(opt->num_supported, opt->supported_values); GVariant *tuple_variant = g_variant_new_tuple(t, 5); g_free(t); free(group_name); return tuple_variant; } GVariant *pack_media(const Media *media) { GVariant **t = g_new(GVariant *, 5); t[0] = g_variant_new_string(media->name); t[1] = g_variant_new_int32(media->width); t[2] = g_variant_new_int32(media->length); t[3] = g_variant_new_int32(media->num_margins); t[4] = cpdbPackMediaArray(media->num_margins, media->margins); GVariant *tuple_variant = g_variant_new_tuple(t, 5); g_free(t); return tuple_variant; } int get_all_options(PrinterCUPS *p, Option **options) { ensure_printer_connection(p); char **option_names; int num_options = get_job_creation_attributes(p, &option_names); /** number of options to be returned**/ /** Addition options not present in "job-creation-attributes" **/ char *additional_options[] = {"media-source", "media-type"}; int sz = sizeof(additional_options) / sizeof(char *); /** Add additional attributes to current option_names list **/ option_names = realloc(option_names, sizeof(char *) * (num_options+sz)); for (int i=0; ihttp, p->dest, p->dinfo, option_names[i]); if (vals) opts[optsIndex].num_supported = ippGetCount(vals); else opts[optsIndex].num_supported = 0; /** Retreive all the supported values for that option **/ opts[optsIndex].supported_values = cpdbNewCStringArray(opts[optsIndex].num_supported); for (j = 0; j < opts[optsIndex].num_supported; j++) { opts[optsIndex].supported_values[j] = extract_ipp_attribute(vals, j, option_names[i]); if (opts[optsIndex].supported_values[j] == NULL) { opts[optsIndex].supported_values[j] = cpdbGetStringCopy("NA"); } } /** Retrieve the default value for that option **/ opts[optsIndex].default_value = get_default(p, option_names[i]); if (opts[optsIndex].default_value == NULL) { opts[optsIndex].default_value = cpdbGetStringCopy("NA"); } optsIndex++; } /* Add the booklet option */ opts[optsIndex].option_name = cpdbGetStringCopy("booklet"); opts[optsIndex].num_supported = 3; opts[optsIndex].supported_values = cpdbNewCStringArray(opts[optsIndex].num_supported); opts[optsIndex].supported_values[0] = cpdbGetStringCopy("off"); opts[optsIndex].supported_values[1] = cpdbGetStringCopy("on"); opts[optsIndex].supported_values[2] = cpdbGetStringCopy("shuffle-only"); opts[optsIndex].default_value = get_default(p, opts[optsIndex].option_name); if (strcmp(opts[optsIndex].default_value, "NA") == 0) { opts[optsIndex].default_value = cpdbGetStringCopy(opts[optsIndex].supported_values[0]); } optsIndex++; /* Add the ipp-attribute-fidelity option */ opts[optsIndex].option_name = cpdbGetStringCopy("ipp-attribute-fidelity"); opts[optsIndex].num_supported = 2; opts[optsIndex].supported_values = cpdbNewCStringArray(opts[optsIndex].num_supported); opts[optsIndex].supported_values[0] = cpdbGetStringCopy("off"); opts[optsIndex].supported_values[1] = cpdbGetStringCopy("on"); opts[optsIndex].default_value = get_default(p, opts[optsIndex].option_name); if (strcmp(opts[optsIndex].default_value, "NA") == 0) { opts[optsIndex].default_value = cpdbGetStringCopy(opts[optsIndex].supported_values[0]); } optsIndex++; /* Add the job-sheets option */ opts[optsIndex].option_name = cpdbGetStringCopy("job-sheets"); opts[optsIndex].num_supported = 8; opts[optsIndex].supported_values = cpdbNewCStringArray(opts[optsIndex].num_supported); opts[optsIndex].supported_values[0] = cpdbGetStringCopy("none"); opts[optsIndex].supported_values[1] = cpdbGetStringCopy("classified"); opts[optsIndex].supported_values[2] = cpdbGetStringCopy("confidential"); opts[optsIndex].supported_values[3] = cpdbGetStringCopy("form"); opts[optsIndex].supported_values[4] = cpdbGetStringCopy("secret"); opts[optsIndex].supported_values[5] = cpdbGetStringCopy("standard"); opts[optsIndex].supported_values[6] = cpdbGetStringCopy("topsecret"); opts[optsIndex].supported_values[7] = cpdbGetStringCopy("unclassified"); opts[optsIndex].default_value = get_default(p, opts[optsIndex].option_name); if (strcmp(opts[optsIndex].default_value, "NA") == 0) { opts[optsIndex].default_value = cpdbGetStringCopy("none,none"); } optsIndex++; /* Add the mirror option */ opts[optsIndex].option_name = cpdbGetStringCopy("mirror"); opts[optsIndex].num_supported = 2; opts[optsIndex].supported_values = cpdbNewCStringArray(opts[optsIndex].num_supported); opts[optsIndex].supported_values[0] = cpdbGetStringCopy("off"); opts[optsIndex].supported_values[1] = cpdbGetStringCopy("on"); opts[optsIndex].default_value = get_default(p, opts[optsIndex].option_name); if (strcmp(opts[optsIndex].default_value, "NA") == 0) { opts[optsIndex].default_value = cpdbGetStringCopy(opts[optsIndex].supported_values[0]); } optsIndex++; /* Add the multiple-document-handling option */ opts[optsIndex].option_name = cpdbGetStringCopy("multiple-document-handling"); opts[optsIndex].num_supported = 2; opts[optsIndex].supported_values = cpdbNewCStringArray(opts[optsIndex].num_supported); opts[optsIndex].supported_values[0] = cpdbGetStringCopy("separate-documents-uncollated-copies"); opts[optsIndex].supported_values[1] = cpdbGetStringCopy("separate-documents-collated-copies"); opts[optsIndex].default_value = get_default(p, opts[optsIndex].option_name); if (strcmp(opts[optsIndex].default_value, "NA") == 0) { opts[optsIndex].default_value = cpdbGetStringCopy(opts[optsIndex].supported_values[0]); } optsIndex++; /* Add the number-up option */ opts[optsIndex].option_name = cpdbGetStringCopy("number-up"); opts[optsIndex].num_supported = 6; opts[optsIndex].supported_values = cpdbNewCStringArray(opts[optsIndex].num_supported); opts[optsIndex].supported_values[0] = cpdbGetStringCopy("1"); opts[optsIndex].supported_values[1] = cpdbGetStringCopy("2"); opts[optsIndex].supported_values[2] = cpdbGetStringCopy("4"); opts[optsIndex].supported_values[3] = cpdbGetStringCopy("6"); opts[optsIndex].supported_values[4] = cpdbGetStringCopy("9"); opts[optsIndex].supported_values[5] = cpdbGetStringCopy("16"); opts[optsIndex].default_value = get_default(p, opts[optsIndex].option_name); if (strcmp(opts[optsIndex].default_value, "NA") == 0) { opts[optsIndex].default_value = cpdbGetStringCopy(opts[optsIndex].supported_values[0]); } optsIndex++; /* Add the number-up-layout option */ opts[optsIndex].option_name = cpdbGetStringCopy("number-up-layout"); opts[optsIndex].num_supported = 8; opts[optsIndex].supported_values = cpdbNewCStringArray(opts[optsIndex].num_supported); opts[optsIndex].supported_values[0] = cpdbGetStringCopy("lrtb"); opts[optsIndex].supported_values[1] = cpdbGetStringCopy("lrbt"); opts[optsIndex].supported_values[2] = cpdbGetStringCopy("rltb"); opts[optsIndex].supported_values[3] = cpdbGetStringCopy("rlbt"); opts[optsIndex].supported_values[4] = cpdbGetStringCopy("tblr"); opts[optsIndex].supported_values[5] = cpdbGetStringCopy("tbrl"); opts[optsIndex].supported_values[6] = cpdbGetStringCopy("btlr"); opts[optsIndex].supported_values[7] = cpdbGetStringCopy("btrl"); opts[optsIndex].default_value = get_default(p, opts[optsIndex].option_name); if (strcmp(opts[optsIndex].default_value, "NA") == 0) { opts[optsIndex].default_value = cpdbGetStringCopy(opts[optsIndex].supported_values[0]); } optsIndex++; /* Add the orientation-requested option */ opts[optsIndex].option_name = cpdbGetStringCopy("orientation-requested"); opts[optsIndex].num_supported = 4; opts[optsIndex].supported_values = cpdbNewCStringArray(opts[optsIndex].num_supported); opts[optsIndex].supported_values[0] = cpdbGetStringCopy("3"); opts[optsIndex].supported_values[1] = cpdbGetStringCopy("4"); opts[optsIndex].supported_values[2] = cpdbGetStringCopy("5"); opts[optsIndex].supported_values[3] = cpdbGetStringCopy("6"); opts[optsIndex].default_value = get_default(p, opts[optsIndex].option_name); if (strcmp(opts[optsIndex].default_value, "NA") == 0) { opts[optsIndex].default_value = cpdbGetStringCopy(opts[optsIndex].supported_values[0]); } else { if (strcmp(opts[optsIndex].default_value, "potrait") == 0) opts[optsIndex].default_value = cpdbGetStringCopy(opts[optsIndex].supported_values[0]); else if (strcmp(opts[optsIndex].default_value, "landscape") == 0) opts[optsIndex].default_value = cpdbGetStringCopy(opts[optsIndex].supported_values[1]); else if (strcmp(opts[optsIndex].default_value, "reverse-landscape") == 0) opts[optsIndex].default_value = cpdbGetStringCopy(opts[optsIndex].supported_values[2]); else if (strcmp(opts[optsIndex].default_value, "reverse-potrait") == 0) opts[optsIndex].default_value = cpdbGetStringCopy(opts[optsIndex].supported_values[3]); else opts[optsIndex].default_value = cpdbGetStringCopy(opts[optsIndex].supported_values[0]); } optsIndex++; /* Add the page-border option */ opts[optsIndex].option_name = cpdbGetStringCopy("page-border"); opts[optsIndex].num_supported = 5; opts[optsIndex].supported_values = cpdbNewCStringArray(opts[optsIndex].num_supported); opts[optsIndex].supported_values[0] = cpdbGetStringCopy("none"); opts[optsIndex].supported_values[1] = cpdbGetStringCopy("single"); opts[optsIndex].supported_values[2] = cpdbGetStringCopy("single-thick"); opts[optsIndex].supported_values[3] = cpdbGetStringCopy("double"); opts[optsIndex].supported_values[4] = cpdbGetStringCopy("double-thick"); opts[optsIndex].default_value = get_default(p, opts[optsIndex].option_name); if (strcmp(opts[optsIndex].default_value, "NA") == 0) { opts[optsIndex].default_value = cpdbGetStringCopy(opts[optsIndex].supported_values[0]); } optsIndex++; /* Add the page-delivery option */ opts[optsIndex].option_name = cpdbGetStringCopy("page-delivery"); opts[optsIndex].num_supported = 2; opts[optsIndex].supported_values = cpdbNewCStringArray(opts[optsIndex].num_supported); opts[optsIndex].supported_values[0] = cpdbGetStringCopy("same-order"); opts[optsIndex].supported_values[1] = cpdbGetStringCopy("reverse-order"); opts[optsIndex].default_value = get_default(p, opts[optsIndex].option_name); if (strcmp(opts[optsIndex].default_value, "NA") == 0) { opts[optsIndex].default_value = cpdbGetStringCopy(opts[optsIndex].supported_values[0]); } optsIndex++; /* Add the page-set option */ opts[optsIndex].option_name = cpdbGetStringCopy("page-set"); opts[optsIndex].num_supported = 3; opts[optsIndex].supported_values = cpdbNewCStringArray(opts[optsIndex].num_supported); opts[optsIndex].supported_values[0] = cpdbGetStringCopy("all"); opts[optsIndex].supported_values[1] = cpdbGetStringCopy("even"); opts[optsIndex].supported_values[2] = cpdbGetStringCopy("odd"); opts[optsIndex].default_value = get_default(p, opts[optsIndex].option_name); if (strcmp(opts[optsIndex].default_value, "NA") == 0) { opts[optsIndex].default_value = cpdbGetStringCopy(opts[optsIndex].supported_values[0]); } optsIndex++; /* Add the position option */ opts[optsIndex].option_name = cpdbGetStringCopy("position"); opts[optsIndex].num_supported = 9; opts[optsIndex].supported_values = cpdbNewCStringArray(opts[optsIndex].num_supported); opts[optsIndex].supported_values[0] = cpdbGetStringCopy("center"); opts[optsIndex].supported_values[1] = cpdbGetStringCopy("top"); opts[optsIndex].supported_values[2] = cpdbGetStringCopy("bottom"); opts[optsIndex].supported_values[3] = cpdbGetStringCopy("left"); opts[optsIndex].supported_values[4] = cpdbGetStringCopy("right"); opts[optsIndex].supported_values[5] = cpdbGetStringCopy("top-left"); opts[optsIndex].supported_values[6] = cpdbGetStringCopy("top-right"); opts[optsIndex].supported_values[7] = cpdbGetStringCopy("bottom-left"); opts[optsIndex].supported_values[8] = cpdbGetStringCopy("bottom-right"); opts[optsIndex].default_value = get_default(p, opts[optsIndex].option_name); if (strcmp(opts[optsIndex].default_value, "NA") == 0) { opts[optsIndex].default_value = cpdbGetStringCopy(opts[optsIndex].supported_values[0]); } optsIndex++; /* Add the print-scaling option */ opts[optsIndex].option_name = cpdbGetStringCopy("print-scaling"); opts[optsIndex].num_supported = 5; opts[optsIndex].supported_values = cpdbNewCStringArray(opts[optsIndex].num_supported); opts[optsIndex].supported_values[0] = cpdbGetStringCopy("auto"); opts[optsIndex].supported_values[1] = cpdbGetStringCopy("auto-fit"); opts[optsIndex].supported_values[2] = cpdbGetStringCopy("fill"); opts[optsIndex].supported_values[3] = cpdbGetStringCopy("fit"); opts[optsIndex].supported_values[4] = cpdbGetStringCopy("none"); opts[optsIndex].default_value = get_default(p, opts[optsIndex].option_name); if (strcmp(opts[optsIndex].default_value, "NA") == 0) { opts[optsIndex].default_value = cpdbGetStringCopy(opts[optsIndex].supported_values[0]); } optsIndex++; /* Add the billing-info option */ opts[optsIndex].option_name = cpdbGetStringCopy("billing-info"); opts[optsIndex].num_supported = 0; opts[optsIndex].supported_values = NULL; opts[optsIndex].default_value = get_default(p, opts[optsIndex].option_name); if (strcmp(opts[optsIndex].default_value, "NA") == 0) { opts[optsIndex].default_value = cpdbGetStringCopy(""); } optsIndex++; /* Correct the print-quality option */ for (i = 0; i < optsIndex; i++) { if (strcmp(opts[i].option_name, "print-quality") == 0) { for (j = 0; j < opts[i].num_supported; j++) { if (strcasecmp(opts[i].supported_values[j], "draft") == 0) { free(opts[i].supported_values[j]); opts[i].supported_values[j] = cpdbGetStringCopy("3"); continue; } if (strcasecmp(opts[i].supported_values[j], "normal") == 0) { free(opts[i].supported_values[j]); opts[i].supported_values[j] = cpdbGetStringCopy("4"); continue; } if (strcasecmp(opts[i].supported_values[j], "high") == 0) { free(opts[i].supported_values[j]); opts[i].supported_values[j] = cpdbGetStringCopy("5"); continue; } } if (strcasecmp(opts[i].default_value, "draft") == 0) { free(opts[i].default_value); opts[i].default_value = cpdbGetStringCopy("3"); continue; } if (strcasecmp(opts[i].default_value, "normal") == 0) { free(opts[i].default_value); opts[i].default_value = cpdbGetStringCopy("4"); continue; } if (strcasecmp(opts[i].default_value, "high") == 0) { free(opts[i].default_value); opts[i].default_value = cpdbGetStringCopy("5"); continue; } break; } } *options = (Option *) realloc(opts, sizeof(Option) * optsIndex); return optsIndex; } int get_all_media(PrinterCUPS *p, Media **medias) { ensure_printer_connection(p); ipp_t *request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES); const char *uri = cupsGetOption("printer-uri-supported", p->dest->num_options, p->dest->options); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); const char *const requested_attributes[] = {"media-col-database"}; ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", 1, NULL, requested_attributes); ipp_t *response = cupsDoRequest(p->http, request, "/"); if (cupsLastError() >= IPP_STATUS_ERROR_BAD_REQUEST) { /* request failed */ printf("Request failed: %s\n", cupsLastErrorString()); return 0; } int media_num = 0; /** Number of unqiue media sizes **/ Media *meds = NULL; /** Array of unique media sizes **/ ipp_attribute_t *mdb; // media database if ((mdb = ippFindAttribute(response, "media-col-database", IPP_TAG_BEGIN_COLLECTION)) != NULL) { int i, j; gpointer key, value; /** Looping variables **/ const char *name; /** PWG name for a media **/ int width, length; /** Width and length of a media **/ pwg_media_t *pwg_media; ipp_t *tuple; /** Single media entry in media-col-database **/ ipp_t *media_size; /** media-size collection in a media tuple **/ ipp_attribute_t *attr; /** Temporary variable for ipp attributes in a single tuple **/ typedef struct Margin { int left; int right; int top; int bottom; } Margin; Margin *margin; /** Single margin struct for some media size**/ GList *margins, *listIter; /** List of all different margins for some media size **/ GHashTable *table; /** [media-size]-->[margins] **/ GHashTableIter iter; /** For iterating over the table **/ table = g_hash_table_new(g_str_hash, g_str_equal); int count = ippGetCount(mdb); for (int i = 0; i < count; i++) { tuple = ippGetCollection(mdb, i); attr = ippFindAttribute(tuple, "media-size", IPP_TAG_BEGIN_COLLECTION); media_size = ippGetCollection(attr, 0); attr = ippFindAttribute(media_size, "x-dimension", IPP_TAG_INTEGER); width = ippGetInteger(attr, 0); attr = ippFindAttribute(media_size, "y-dimension", IPP_TAG_INTEGER); length = ippGetInteger(attr, 0); if (width <= 0 || length <= 0) continue; pwg_media = pwgMediaForSize(width, length); name = pwg_media->pwg; margin = g_new0(Margin, 1); attr = ippFindAttribute(tuple, "media-left-margin", IPP_TAG_INTEGER); margin->left = ippGetInteger(attr, 0); attr = ippFindAttribute(tuple, "media-right-margin", IPP_TAG_INTEGER); margin->right = ippGetInteger(attr, 0); attr = ippFindAttribute(tuple, "media-top-margin", IPP_TAG_INTEGER); margin->top = ippGetInteger(attr, 0); attr = ippFindAttribute(tuple, "media-bottom-margin", IPP_TAG_INTEGER); margin->bottom = ippGetInteger(attr, 0); margins = g_hash_table_lookup(table, name); margins = g_list_prepend(margins, margin); g_hash_table_replace(table, (gpointer) name, margins); } media_num = g_hash_table_size(table); meds = g_new0 (Media, media_num); i = 0; g_hash_table_iter_init(&iter, table); while (g_hash_table_iter_next(&iter, &key, &value)) { name = (char *) key; pwg_media = pwgMediaForPWG(name); margins = (GList *) value; margins = g_list_reverse(margins); meds[i].name = cpdbGetStringCopy(name); meds[i].width = pwg_media->width; meds[i].length = pwg_media->length; meds[i].num_margins = g_list_length(margins); meds[i].margins = malloc(sizeof(int) * meds[i].num_margins * 4); j = 0; listIter = margins; while (listIter != NULL) { margin = (Margin *) listIter->data; meds[i].margins[j][0] = margin->left; meds[i].margins[j][1] = margin->right; meds[i].margins[j][2] = margin->top; meds[i].margins[j][3] = margin->bottom; free(margin); listIter = listIter->next; j++; } g_list_free(margins); i++; } g_hash_table_destroy(table); } ippDelete(response); *medias = meds; return media_num; } int add_media_to_options(PrinterCUPS *p, Media *medias, int media_count, Option **options, int count) { int i, j; /** Looping variables **/ int num_media; /** Variable for number of "media" supported using CUPS call **/ char *media_name; /** Variable for media name **/ int width, length; /** Variable for media width and media length **/ int optsIndex = count; /** Index for fillings options **/ pwg_media_t *pwg_media; ipp_t *media_col, *media_size; /** media_col and media_size collections in IPP request **/ ipp_attribute_t *vals, *default_val, *attr; count += 5; /** "media", "media-{top, bottom, left, right}-margins" **/ Option *opts = *options; opts = realloc(opts, sizeof(Option) * count); /* Add the media option */ opts[optsIndex].option_name = cpdbGetStringCopy("media"); opts[optsIndex].num_supported = media_count; opts[optsIndex].supported_values = cpdbNewCStringArray(opts[optsIndex].num_supported + 2); /** 2 extra for custom_min and custom_max sizes **/ for (i = 0; i < opts[optsIndex].num_supported; i++) { opts[optsIndex].supported_values[i] = cpdbGetStringCopy(medias[i].name); } opts[optsIndex].default_value = get_default(p, "media"); if (opts[optsIndex].default_value == NULL) { opts[optsIndex].default_value = cpdbGetStringCopy("NA"); } /** Add custom_min and custom_max media if they exist **/ vals = cupsFindDestSupported(p->http, p->dest, p->dinfo, "media"); if (vals) num_media = ippGetCount(vals); else num_media = 0; for (j = 0; j < num_media && i < (media_count + 2); j++) { media_name = extract_ipp_attribute(vals, i, "media"); if (media_name == NULL) continue; if (strncmp(media_name, "custom_min", 10) == 0 || strncmp(media_name, "custom_max", 10) == 0) { opts[optsIndex].supported_values[i] = cpdbGetStringCopy(media_name); i++; } free(media_name); } opts[optsIndex].num_supported = media_count = i; optsIndex++; /* Add the media-{top,left,right,bottom}-margin option */ char def[16]; char *attrs[] = {"media-left-margin", "media-bottom-margin", "media-top-margin", "media-right-margin"}; default_val = cupsFindDestDefault(p->http, p->dest, p->dinfo, "media-col"); for (i = 0; i < 4; i++) // for each attr in attrs { vals = cupsFindDestSupported(p->http, p->dest, p->dinfo, attrs[i]); opts[optsIndex].option_name = cpdbGetStringCopy(attrs[i]); if (vals) opts[optsIndex].num_supported = ippGetCount(vals); else opts[optsIndex].num_supported = 0; opts[optsIndex].supported_values = cpdbNewCStringArray(opts[optsIndex].num_supported); for (j = 0; j < opts[optsIndex].num_supported; j++) { opts[optsIndex].supported_values[j] = extract_ipp_attribute(vals, j, attrs[i]); if (opts[optsIndex].supported_values[j] == NULL) { opts[optsIndex].supported_values[j] = cpdbGetStringCopy("NA"); } } media_col = ippGetCollection(default_val, 0); attr = ippFindAttribute(media_col, attrs[i], IPP_TAG_INTEGER); snprintf(def, 16, "%d", ippGetInteger(attr, 0)); opts[optsIndex].default_value = cpdbGetStringCopy(def); if (opts[optsIndex].default_value == NULL) { opts[optsIndex].default_value = cpdbGetStringCopy("NA"); } optsIndex++; } *options = opts; return count; } const char *get_printer_state(PrinterCUPS *p) { const char *str; ensure_printer_connection(p); ipp_t *request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES); const char *uri = cupsGetOption("printer-uri-supported", p->dest->num_options, p->dest->options); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); const char *const requested_attributes[] = {"printer-state"}; ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", 1, NULL, requested_attributes); ipp_t *response = cupsDoRequest(p->http, request, "/"); if (cupsLastError() >= IPP_STATUS_ERROR_BAD_REQUEST) { /* request failed */ printf("Request failed: %s\n", cupsLastErrorString()); return "NA"; } ipp_attribute_t *attr; if ((attr = ippFindAttribute(response, "printer-state", IPP_TAG_ENUM)) != NULL) { printf("printer-state=%d\n", ippGetInteger(attr, 0)); str = map->state[ippGetInteger(attr, 0)]; } return str; } int print_file(PrinterCUPS *p, const char *file_path, int num_settings, GVariant *settings) { ensure_printer_connection(p); int num_options = 0; cups_option_t *options; GVariantIter *iter; g_variant_get(settings, "a(ss)", &iter); int i = 0; char *option_name, *option_value; for (i = 0; i < num_settings; i++) { g_variant_iter_loop(iter, "(ss)", &option_name, &option_value); printf(" %s : %s\n", option_name, option_value); /** * to do: * instead of directly adding the option,convert it from the frontend's lingo * to the specific lingo of the backend * * use PWG names instead */ num_options = cupsAddOption(option_name, option_value, num_options, &options); } char *file_name = cpdbExtractFileName(file_path); int job_id = 0; cupsCreateDestJob(p->http, p->dest, p->dinfo, &job_id, file_name, num_options, options); if (job_id) { /** job creation was successful , * Now let's submit a document * and start writing data onto it **/ printf("Created job %d\n", job_id); http_status_t http_status; /**document creation status **/ http_status = cupsStartDestDocument(p->http, p->dest, p->dinfo, job_id, file_name, CUPS_FORMAT_AUTO, num_options, options, 1); if (http_status == HTTP_STATUS_CONTINUE) { /**Document submitted successfully; * Now write the data onto it**/ FILE *fp = fopen(file_path, "rb"); size_t bytes; char buffer[65536]; /** Read and write the data chunk by chunk **/ while ((bytes = fread(buffer, 1, sizeof(buffer), fp)) > 0) { http_status = cupsWriteRequestData(p->http, buffer, bytes); if (http_status != HTTP_STATUS_CONTINUE) { printf("Error writing print data to server.\n"); break; } } if (cupsFinishDestDocument(p->http, p->dest, p->dinfo) == IPP_STATUS_OK) printf("Document send succeeded.\n"); else printf("Document send failed: %s\n", cupsLastErrorString()); fclose(fp); return job_id; /**some correction needed here **/ } } else { printf("Unable to create job %s\n", cupsLastErrorString()); return 0; } } int get_active_jobs_count(PrinterCUPS *p) { ensure_printer_connection(p); cups_job_t *jobs; int num_jobs = cupsGetJobs2(p->http, &jobs, p->name, 0, CUPS_WHICHJOBS_ACTIVE); cupsFreeJobs(num_jobs, jobs); return num_jobs; } gboolean cancel_job(PrinterCUPS *p, int jobid) { ensure_printer_connection(p); ipp_status_t status = cupsCancelDestJob(p->http, p->dest, jobid); if (status == IPP_STATUS_OK) return TRUE; return FALSE; } void printAllJobs(PrinterCUPS *p) { ensure_printer_connection(p); cups_job_t *jobs; int num_jobs = cupsGetJobs2(p->http, &jobs, p->name, 1, CUPS_WHICHJOBS_ALL); for (int i = 0; i < num_jobs; i++) { print_job(&jobs[i]); } } static void list_group(ppd_file_t *ppd, /* I - PPD file */ ppd_group_t *group) /* I - Group to show */ { printf("List group %s\n", group->name); /** Now iterate through the options in the particular group*/ printf("It has %d options.\n", group->num_options); printf("Listing all of them ..\n"); int i; for (i = 0; i < group->num_options; i++) { printf(" Option %d : %s\n", i, group->options[i].keyword); } } void tryPPD(PrinterCUPS *p) { const char *filename; /* PPD filename */ ppd_file_t *ppd; /* PPD data */ ppd_group_t *group; /* Current group */ if ((filename = cupsGetPPD(p->dest->name)) == NULL) { printf("Error getting ppd file.\n"); return; } g_message("Got ppd file %s\n", filename); if ((ppd = ppdOpenFile(filename)) == NULL) { printf("Error opening ppd file.\n"); return; } printf("Opened ppd file.\n"); ppdMarkDefaults(ppd); cupsMarkOptions(ppd, p->dest->num_options, p->dest->options); group = ppd->groups; for (int i = ppd->num_groups; i > 0; i--) { /**iterate through all the groups in the ppd file */ list_group(ppd, group); group++; } } /**********Dialog related funtions ****************/ Dialog *get_new_Dialog() { Dialog *d = g_new(Dialog, 1); d->cancel = 0; d->hide_remote = FALSE; d->hide_temp = FALSE; d->keep_alive = FALSE; d->printers = g_hash_table_new_full(g_str_hash, g_str_equal, (GDestroyNotify)free_string, (GDestroyNotify)free_PrinterCUPS); return d; } void free_Dialog(Dialog *d) { printf("freeing dialog..\n"); g_hash_table_destroy(d->printers); free(d); } /*********Mappings********/ Mappings *get_new_Mappings() { Mappings *m = (Mappings *)(malloc(sizeof(Mappings))); m->state[3] = CPDB_STATE_IDLE; m->state[4] = CPDB_STATE_PRINTING; m->state[5] = CPDB_STATE_STOPPED; m->orientation[atoi(CUPS_ORIENTATION_LANDSCAPE)] = CPDB_ORIENTATION_LANDSCAPE; m->orientation[atoi(CUPS_ORIENTATION_PORTRAIT)] = CPDB_ORIENTATION_PORTRAIT; return m; } /*****************CUPS and IPP helpers*********************/ const char *cups_printer_state(cups_dest_t *dest) { //cups_dest_t *dest = cupsGetNamedDest(CUPS_HTTP_DEFAULT, printer_name, NULL); g_assert_nonnull(dest); const char *state = cupsGetOption("printer-state", dest->num_options, dest->options); if (state == NULL) return "NA"; return map->state[state[0] - '0']; } gboolean cups_is_accepting_jobs(cups_dest_t *dest) { g_assert_nonnull(dest); const char *val = cupsGetOption("printer-is-accepting-jobs", dest->num_options, dest->options); return cpdbGetBoolean(val); } void cups_get_Resolution(cups_dest_t *dest, int *xres, int *yres) { http_t *http = cupsConnectDest(dest, CUPS_DEST_FLAGS_NONE, 500, NULL, NULL, 0, NULL, NULL); g_assert_nonnull(http); cups_dinfo_t *dinfo = cupsCopyDestInfo(http, dest); g_assert_nonnull(dinfo); ipp_attribute_t *attr = cupsFindDestDefault(http, dest, dinfo, "printer-resolution"); ipp_res_t *units; *xres = ippGetResolution(attr, 0, yres, units); } int add_printer_to_ht(void *user_data, unsigned flags, cups_dest_t *dest) { GHashTable *h = (GHashTable *)user_data; char *printername = cpdbGetStringCopy(dest->name); cups_dest_t *dest_copy = NULL; cupsCopyDest(dest, 0, &dest_copy); g_hash_table_insert(h, printername, dest_copy); return 1; } int add_printer_to_ht_no_temp(void *user_data, unsigned flags, cups_dest_t *dest) { if (cups_is_temporary(dest)) return 1; GHashTable *h = (GHashTable *)user_data; char *printername = cpdbGetStringCopy(dest->name); cups_dest_t *dest_copy = NULL; cupsCopyDest(dest, 0, &dest_copy); g_hash_table_insert(h, printername, dest_copy); return 1; } GHashTable *cups_get_printers(gboolean notemp, gboolean noremote) { cups_dest_cb_t cb = add_printer_to_ht; unsigned type = 0, mask = 0; if (noremote) { type = CUPS_PRINTER_LOCAL; mask = CUPS_PRINTER_REMOTE; } if (notemp) { cb = add_printer_to_ht_no_temp; } GHashTable *printers_ht = g_hash_table_new(g_str_hash, g_str_equal); cupsEnumDests(CUPS_DEST_FLAGS_NONE, 1000, //timeout NULL, //cancel type, //TYPE mask, //MASK cb, //function printers_ht); //user_data return printers_ht; } GHashTable *cups_get_all_printers() { printf("all printers\n"); // to do : fix GHashTable *printers_ht = g_hash_table_new(g_str_hash, g_str_equal); cupsEnumDests(CUPS_DEST_FLAGS_NONE, 3000, //timeout NULL, //cancel 0, //TYPE 0, //MASK add_printer_to_ht, //function printers_ht); //user_data return printers_ht; } GHashTable *cups_get_local_printers() { printf("local printers\n"); //to do: fix GHashTable *printers_ht = g_hash_table_new(g_str_hash, g_str_equal); cupsEnumDests(CUPS_DEST_FLAGS_NONE, 1200, //timeout NULL, //cancel CUPS_PRINTER_LOCAL, //TYPE CUPS_PRINTER_REMOTE, //MASK add_printer_to_ht, //function printers_ht); //user_data return printers_ht; } char *cups_retrieve_string(cups_dest_t *dest, const char *option_name) { /** this funtion is kind of a wrapper , to ensure that the return value is never NULL , as that can cause the backend to segFault **/ g_assert_nonnull(dest); g_assert_nonnull(option_name); char *ans = NULL; ans = cpdbGetStringCopy(cupsGetOption(option_name, dest->num_options, dest->options)); if (ans) return ans; return cpdbGetStringCopy("NA"); } gboolean cups_is_temporary(cups_dest_t *dest) { g_assert_nonnull(dest); if (cupsGetOption("printer-uri-supported", dest->num_options, dest->options)) return FALSE; return TRUE; } char *extract_ipp_attribute(ipp_attribute_t *attr, int index, const char *option_name) { /** first deal with the totally unique cases **/ if (strcmp(option_name, CUPS_ORIENTATION) == 0) return extract_orientation_from_ipp(attr, index); /** Then deal with the generic cases **/ char *str; const char *attrstr; switch (ippGetValueTag(attr)) { case IPP_TAG_INTEGER: str = (char *)(malloc(sizeof(char) * 50)); snprintf(str, sizeof(str), "%d", ippGetInteger(attr, index)); break; case IPP_TAG_ENUM: attrstr = ippEnumString(option_name, ippGetInteger(attr, index)); str = strdup(attrstr); break; case IPP_TAG_RANGE: str = (char *)(malloc(sizeof(char) * 100)); int upper, lower = ippGetRange(attr, index, &upper); snprintf(str, sizeof(str), "%d-%d", lower, upper); break; case IPP_TAG_RESOLUTION: return extract_res_from_ipp(attr, index); default: return extract_string_from_ipp(attr, index); } return str; } char *extract_res_from_ipp(ipp_attribute_t *attr, int index) { int xres, yres; ipp_res_t units; xres = ippGetResolution(attr, index, &yres, &units); char *unit = units == IPP_RES_PER_INCH ? "dpi" : "dpcm"; char buf[100]; if (xres == yres) snprintf(buf, sizeof(buf), "%d%s", xres, unit); else snprintf(buf, sizeof(buf), "%dx%d%s", xres, yres, unit); return cpdbGetStringCopy(buf); } char *extract_string_from_ipp(ipp_attribute_t *attr, int index) { return cpdbGetStringCopy(ippGetString(attr, index, NULL)); } char *extract_orientation_from_ipp(ipp_attribute_t *attr, int index) { char *str = cpdbGetStringCopy(ippEnumString(CUPS_ORIENTATION, ippGetInteger(attr, index))); if (strcmp("0", str) == 0) str = cpdbGetStringCopy("automatic-rotation"); return str; } void print_job(cups_job_t *j) { printf("title : %s\n", j->title); printf("dest : %s\n", j->dest); printf("job-id : %d\n", j->id); printf("user : %s\n", j->user); char *state = translate_job_state(j->state); printf("state : %s\n", state); } char *get_option_translation(PrinterCUPS *p, const char *option_name, const char *locale) { char *copy; const char *uri, *translation; static const char *const req_attrs[] = {"printer-strings-uri"}; ipp_attribute_t *attr; ipp_t *request, *response; cups_array_t *opts_catalog, *printer_opts_catalog; ensure_printer_connection(p); request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES); uri = cupsGetOption("printer-uri-supported", p->dest->num_options, p->dest->options); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", 1, NULL, req_attrs); response = cupsDoRequest(p->http, request, "/"); if (cupsLastError() >= IPP_STATUS_ERROR_BAD_REQUEST) { /* request failed */ printf("Request failed: %s\n", cupsLastErrorString()); return cpdbGetStringCopy(option_name); } opts_catalog = cfCatalogOptionArrayNew(); cfCatalogLoad(NULL, locale, opts_catalog); if ((attr = ippFindAttribute(response, "printer-strings-uri", IPP_TAG_URI)) != NULL) { printer_opts_catalog = cfCatalogOptionArrayNew(); cfCatalogLoad(ippGetString(attr, 0, NULL), NULL, printer_opts_catalog); } translation = cfCatalogLookUpOption((char *)option_name, opts_catalog, printer_opts_catalog); copy = cpdbGetStringCopy(translation); cupsArrayDelete(opts_catalog); cupsArrayDelete(printer_opts_catalog); return copy; } char *get_choice_translation(PrinterCUPS *p, const char *option_name, const char *choice_name, const char *locale) { char *copy; const char *uri, *translation; static const char *const req_attrs[] = {"printer-strings-uri"}; ipp_attribute_t *attr; ipp_t *request, *response; cups_array_t *opts_catalog, *printer_opts_catalog; ensure_printer_connection(p); request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES); uri = cupsGetOption("printer-uri-supported", p->dest->num_options, p->dest->options); ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", 1, NULL, req_attrs); response = cupsDoRequest(p->http, request, "/"); if (cupsLastError() >= IPP_STATUS_ERROR_BAD_REQUEST) { /* request failed */ printf("Request failed: %s\n", cupsLastErrorString()); return cpdbGetStringCopy(choice_name); } opts_catalog = cfCatalogOptionArrayNew(); cfCatalogLoad(NULL, locale, opts_catalog); if ((attr = ippFindAttribute(response, "printer-strings-uri", IPP_TAG_URI)) != NULL) { printer_opts_catalog = cfCatalogOptionArrayNew(); cfCatalogLoad(ippGetString(attr, 0, NULL), NULL, printer_opts_catalog); } translation = cfCatalogLookUpChoice((char *)choice_name, (char *)option_name, opts_catalog, printer_opts_catalog); copy = cpdbGetStringCopy(translation); cupsArrayDelete(opts_catalog); cupsArrayDelete(printer_opts_catalog); return copy; } GVariant *get_printer_translations(PrinterCUPS *p, const char *locale) { int num_opts; Option *opts; GVariant *translations; GVariantBuilder *builder; char *group; char *name_tr, *group_tr, *choice_tr; char *name_key, *group_key, *choice_key; num_opts = get_all_options(p, &opts); builder = g_variant_builder_new(G_VARIANT_TYPE(CPDB_TL_DICT_ARGS)); for (int i = 0; i < num_opts; i++) { /* add translation for option name */ name_tr = get_option_translation(p, opts[i].option_name, locale); name_key = cpdbConcatSep(CPDB_OPT_PREFIX, opts[i].option_name); if (name_tr) { logdebug("Translation '%s' : '%s'\n", name_key, name_tr); g_variant_builder_add(builder, CPDB_TL_ARGS, name_key, name_tr); } g_free(name_tr); /* add translation for option group */ group = cpdbGetGroup(opts[i].option_name); group_key = cpdbConcatSep(CPDB_GRP_PREFIX, group); group_tr = cpdbGetGroupTranslation2(group, locale); if (group_tr) { logdebug("Translation '%s' : '%s'\n", group_key, group_tr); g_variant_builder_add(builder, CPDB_TL_ARGS, group_key, group_tr); } g_free(group); g_free(group_key); g_free(group_tr); /* add translation for option choices */ for (int j = 0; j < opts[i].num_supported; j++) { choice_tr = get_choice_translation(p, opts[i].option_name, opts[i].supported_values[j], locale); choice_key = cpdbConcatSep(name_key, opts[i].supported_values[j]); if (choice_tr) { logdebug("Translation '%s' : '%s'\n", choice_key, choice_tr); g_variant_builder_add(builder, CPDB_TL_ARGS, choice_key, choice_tr); } g_free(choice_key); g_free(choice_tr); } g_free(name_key); } translations = g_variant_builder_end(builder); free_options(num_opts, opts); return translations; } char *translate_job_state(ipp_jstate_t state) { switch (state) { case IPP_JSTATE_ABORTED: return CPDB_JOB_STATE_ABORTED; case IPP_JSTATE_CANCELED: return CPDB_JOB_STATE_CANCELLED; case IPP_JSTATE_HELD: return CPDB_JOB_STATE_HELD; case IPP_JSTATE_PENDING: return CPDB_JOB_STATE_PENDING; case IPP_JSTATE_PROCESSING: return CPDB_JOB_STATE_PRINTING; case IPP_JSTATE_STOPPED: return CPDB_JOB_STATE_STOPPED; case IPP_JSTATE_COMPLETED: return CPDB_JOB_STATE_COMPLETED; default: return "NA"; } } GVariant *pack_cups_job(cups_job_t job) { printf("%s\n", job.dest); GVariant **t = g_new0(GVariant *, 7); char jobid[20]; snprintf(jobid, sizeof(jobid), "%d", job.id); t[0] = g_variant_new_string(jobid); t[1] = g_variant_new_string(job.title); t[2] = g_variant_new_string(job.dest); t[3] = g_variant_new_string(job.user); t[4] = g_variant_new_string(translate_job_state(job.state)); t[5] = g_variant_new_string(httpGetDateString(job.creation_time)); t[6] = g_variant_new_int32(job.size); GVariant *tuple_variant = g_variant_new_tuple(t, 7); g_free(t); return tuple_variant; } void MSG_LOG(const char *msg, int msg_level) { if (MSG_LOG_LEVEL >= msg_level) { printf("%s\n", msg); fflush(stdout); } } void free_string(char *str) { if (str) { free(str); } } cpdb-backend-cups-2.0b5/src/backend_helper.h000066400000000000000000000224031446254674500207570ustar00rootroot00000000000000#ifndef _BACKEND_HELPER_H #define _BACKEND_HELPER_H #include #include #include #include #include #include #include #include /* For cups-notifier */ #define NOTIFY_LEASE_DURATION (24 * 60 * 60) #define CUPS_DBUS_PATH "/org/cups/cupsd/Notifier" /* New Debug macros */ #define BACKEND_NAME "CUPS" #define logdebug(...) cpdbBDebugPrintf(CPDB_DEBUG_LEVEL_DEBUG, BACKEND_NAME, __VA_ARGS__) #define loginfo(...) cpdbBDebugPrintf(CPDB_DEBUG_LEVEL_INFO, BACKEND_NAME, __VA_ARGS__) #define logwarn(...) cpdbBDebugPrintf(CPDB_DEBUG_LEVEL_WARN, BACKEND_NAME, __VA_ARGS__) #define logerror(...) cpdbBDebugPrintf(CPDB_DEBUG_LEVEL_ERROR, BACKEND_NAME, __VA_ARGS__) /* Old debug macros */ #define INFO 3 #define WARN 2 #define ERR 1 #define MSG_LOG_LEVEL INFO /** * Represents a CUPS Printer */ typedef struct _PrinterCUPS { char *name; cups_dest_t *dest; http_t *http; cups_dinfo_t *dinfo; } PrinterCUPS; /** * Represents a frontend instance that the backend is associated with */ typedef struct _Dialog { int cancel; gboolean hide_remote; gboolean hide_temp; GHashTable *printers; gboolean keep_alive; } Dialog; typedef struct _Mappings { GHashTable *media; GHashTable *color; GHashTable *print_quality; const char *orientation[10]; const char *state[6]; } Mappings; /** * Represents the CUPS Backend */ typedef struct _BackendObj { GDBusConnection *dbus_connection; PrintBackend *skeleton; char *obj_path; /** the hash table to map from dialog name(char*) to the Dialog struct(Dialog*) **/ GHashTable *dialogs; int num_frontends; char *default_printer; } BackendObj; /** * Represents a single 'option' for a printer */ typedef struct _Option { char *option_name; int num_supported; char **supported_values; char *default_value; } Option; /** * Represents a single 'media' size for a printer and supported margins */ typedef struct _Media { char *name; int width; int length; int num_margins; int (*margins)[4]; /** int margins[num_margins][4]; left(0), right(1), top(2), bottom(3) **/ } Media; /********Backend related functions*******************/ /** Get a new BackendObj **/ BackendObj *get_new_BackendObj(); /** Get the printer-id of the default printer of the CUPS Backend**/ char *get_default_printer(BackendObj *b); /** Connect the BackendObj to the dbus **/ void connect_to_dbus(BackendObj *, char *obj_path); /** Add the dialog to the list of dialogs of the particular backend**/ void add_frontend(BackendObj *, const char *dialog_name); /** Remove the dialog from the list of frontends that this backend is * associated with. */ void remove_frontend(BackendObj *, const char *dialog_name); /** Checks if the backend isn't associated with any frontend **/ gboolean no_frontends(BackendObj *); /** * Find the dialog with the specified name */ Dialog* find_dialog(BackendObj * , const char* dialog_name); /** Get the variable which controls the cancellation of the enumeration thread for * that particular dialog */ int *get_dialog_cancel(BackendObj *, const char *dialog_name); void set_dialog_cancel(BackendObj *, const char *dialog_name); //make cancel = 0 void reset_dialog_cancel(BackendObj *, const char *dialog_name); //make cancel = 1 /** Returns whether remote CUPS printers are hidden for this dialog **/ gboolean get_hide_remote(BackendObj *b, char *dialog_name); /** Hides remote CUPS printers for the dialog **/ void set_hide_remote_printers(BackendObj *, const char *dialog_name); /** Unhides remote CUPS Printers for the dialog **/ void unset_hide_remote_printers(BackendObj *, const char *dialog_name); /** Returns whether temporary CUPS Queues(discovered, but not set up) are hidden for this dialog **/ gboolean get_hide_temp(BackendObj *b, char *dialog_name); /** Hides temporary CUPS queues for the dialog **/ void set_hide_temp_printers(BackendObj *, const char *dialog_name); /** Unhides temporary CUPS queues for the dialog **/ void unset_hide_temp_printers(BackendObj *, const char *dialog_name); /** Utility functions for subscribing to CUPS for notifications*/ int create_subscription (); gboolean renew_subscription (int id); gboolean renew_subscription_timeout(gpointer userdata); void cancel_subscription (int id); /** * Returns * TRUE if the printer with specified name is found for the dialog * FALSE otherwise */ gboolean dialog_contains_printer(BackendObj *, const char *dialog_name, const char *printer_name); /** * Adds the corresponding CUPS printer to the dialog's printer list * * Returns * the PrinterCUPS* struct added to the dialog * NULL if the operation was unsuccesful */ PrinterCUPS *add_printer_to_dialog(BackendObj *, const char *dialog_name, const cups_dest_t *dest); /** * Removes the printer with the specified name from the dialog's list of printers */ void remove_printer_from_dialog(BackendObj *, const char *dialog_name, const char *printer_name); void send_printer_state_changed_signal(BackendObj *b, const char *dialog_name, const char *printer_name, const char *printer_state, gboolean printer_is_accepting_jobs); void send_printer_added_signal(BackendObj *b, const char *dialog_name, cups_dest_t *dest); void send_printer_removed_signal(BackendObj *b, const char *dialog_name, const char *printer_name); void notify_removed_printers(BackendObj *b, const char *dialog_name, GHashTable *new_table); void notify_added_printers(BackendObj *b, const char *dialog_name, GHashTable *new_table); void replace_printers(BackendObj *b, const char *dialog_name, GHashTable *new_table); void refresh_printer_list(BackendObj *b, char *dialog_name); GHashTable *get_dialog_printers(BackendObj *b, const char *dialog_name); cups_dest_t *get_dest_by_name(BackendObj *b, const char *dialog_name, const char *printer_name); PrinterCUPS *get_printer_by_name(BackendObj *b, const char *dialog_name, const char *printer_name); GVariant *get_all_jobs(BackendObj *b, const char *dialog_name, int *num_jobs, gboolean active_only); /*********Printer related functions******************/ /** Get a new PrinterCUPS struct associated with the cups destination**/ PrinterCUPS *get_new_PrinterCUPS(const cups_dest_t *dest); /** Free up the memory used by the struct **/ void free_PrinterCUPS(PrinterCUPS *); /** Ensure that we have a connection the server**/ gboolean ensure_printer_connection(PrinterCUPS *p); /** * Get state of the printer * state is one of the following {"idle" , "processing" , "stopped"} */ const char *get_printer_state(PrinterCUPS *p); char *get_orientation_default(PrinterCUPS *p); char *get_default(PrinterCUPS *p, char *option_name); int get_supported(PrinterCUPS *p, char ***supported_values, const char *option_name); int get_job_creation_attributes(PrinterCUPS *p, char ***values); int get_all_options(PrinterCUPS *p, Option **options); int get_all_media(PrinterCUPS *p, Media **medias); int add_media_to_options(PrinterCUPS *p, Media *medias, int media_count, Option **options, int count); int print_file(PrinterCUPS *p, const char *file_path, int num_settings, GVariant *settings); int get_active_jobs_count(PrinterCUPS *p); gboolean cancel_job(PrinterCUPS *p, int jobid); /** * Get translation of choice name for a given locale */ char *get_option_translation(PrinterCUPS *p, const char *option_name, const char *locale); /** * Get translation of option name for a given locale */ char *get_choice_translation(PrinterCUPS *p, const char *option_name, const char *choice_name, const char *locale); /** * Get translations for all printer strings */ GVariant *get_printer_translations(PrinterCUPS *p, const char *locale); void tryPPD(PrinterCUPS *p); /**********Dialog related funtions ****************/ Dialog *get_new_Dialog(); void free_Dialog(Dialog *); /*********Option related functions*****************/ void print_option(const Option *opt); void free_options(int count, Option *opts); void unpack_option_array(GVariant *var, int num_options, Option **options); GVariant *pack_option(const Option *opt); GVariant *pack_media(const Media *media); /**********Mapping related functions*****************/ Mappings *get_new_Mappings(); /*************CUPS/IPP RELATED FUNCTIONS******************/ const char *cups_printer_state(cups_dest_t *dest); gboolean cups_is_accepting_jobs(cups_dest_t *dest); void cups_get_Resolution(cups_dest_t *dest, int *xres, int *yres); GHashTable *cups_get_all_printers(); GHashTable *cups_get_local_printers(); char *cups_retrieve_string(cups_dest_t *dest, const char *option_name); gboolean cups_is_temporary(cups_dest_t *dest); GHashTable *cups_get_printers(gboolean notemp, gboolean noremote); char *extract_ipp_attribute(ipp_attribute_t *, int index, const char *option_name); char *extract_res_from_ipp(ipp_attribute_t *, int index); char *extract_string_from_ipp(ipp_attribute_t *attr, int index); char *extract_orientation_from_ipp(ipp_attribute_t *attr, int index); void print_job(cups_job_t *j); GVariant *pack_cups_job(cups_job_t job); char *translate_job_state(ipp_jstate_t); /***************Misc.** **********************/ /**error logging */ void MSG_LOG(const char *msg, int msg_level); void free_string(char *); void free_string_array(int count, char **arr); #endif cpdb-backend-cups-2.0b5/src/print_backend_cups.c000066400000000000000000000752431446254674500216730ustar00rootroot00000000000000#include #include #include #include #include #include "cups-notifier.h" #include #include "backend_helper.h" #define _CUPS_NO_DEPRECATED 1 #define BUS_NAME "org.openprinting.Backend.CUPS" static void on_name_acquired(GDBusConnection *connection, const gchar *name, gpointer not_used); static void acquire_session_bus_name(); gpointer list_printers(gpointer _dialog_name); int send_printer_added(void *_dialog_name, unsigned flags, cups_dest_t *dest); void connect_to_signals(); BackendObj *b; void update_printer_lists() { GHashTableIter iter; gpointer key, value; g_hash_table_iter_init(&iter, b->dialogs); while (g_hash_table_iter_next(&iter, &key, &value)) { char *dialog_name = key; refresh_printer_list(b, dialog_name); } } static void on_printer_state_changed (CupsNotifier *object, const gchar *text, const gchar *printer_uri, const gchar *printer, guint printer_state, const gchar *printer_state_reasons, gboolean printer_is_accepting_jobs, gpointer user_data) { loginfo("Printer state change on printer %s: %s\n", printer, text); GHashTableIter iter; gpointer key, value; g_hash_table_iter_init(&iter, b->dialogs); while (g_hash_table_iter_next(&iter, &key, &value)) { const char *dialog_name = key; PrinterCUPS *p = get_printer_by_name(b, dialog_name, printer); const char *state = get_printer_state(p); send_printer_state_changed_signal(b, dialog_name, printer, state, printer_is_accepting_jobs); } } static void on_printer_added (CupsNotifier *object, const gchar *text, const gchar *printer_uri, const gchar *printer, guint printer_state, const gchar *printer_state_reasons, gboolean printer_is_accepting_jobs, gpointer user_data) { loginfo("Printer added: %s\n", text); update_printer_lists(); } static void on_printer_deleted (CupsNotifier *object, const gchar *text, const gchar *printer_uri, const gchar *printer, guint printer_state, const gchar *printer_state_reasons, gboolean printer_is_accepting_jobs, gpointer user_data) { loginfo("Printer deleted: %s\n", text); update_printer_lists(); } int main() { /* Initialize internal default settings of the CUPS library */ int p = ippPort(); b = get_new_BackendObj(); cpdbInit(); acquire_session_bus_name(BUS_NAME); int subscription_id = create_subscription(); g_timeout_add_seconds (NOTIFY_LEASE_DURATION - 60, renew_subscription_timeout, &subscription_id); GError *error = NULL; CupsNotifier *cups_notifier = cups_notifier_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, 0, NULL, CUPS_DBUS_PATH, NULL, &error); if (error) { logwarn("Error creating cups notify handler: %s", error->message); g_error_free(error); cups_notifier = NULL; } if (cups_notifier != NULL) { g_signal_connect(cups_notifier, "printer-state-changed", G_CALLBACK(on_printer_deleted), NULL); g_signal_connect(cups_notifier, "printer-deleted", G_CALLBACK(on_printer_deleted), NULL); g_signal_connect(cups_notifier, "printer-added", G_CALLBACK(on_printer_added), NULL); } GMainLoop *loop = g_main_loop_new(NULL, FALSE); g_main_loop_run(loop); /* Main loop exited */ logdebug("Main loop exited"); g_main_loop_unref(loop); loop = NULL; cancel_subscription(subscription_id); if (cups_notifier) g_object_unref(cups_notifier); } static void acquire_session_bus_name(char *bus_name) { g_bus_own_name(G_BUS_TYPE_SESSION, bus_name, 0, //flags NULL, //bus_acquired_handler on_name_acquired, //name acquired handler NULL, //name_lost handler NULL, //user_data NULL); //user_data free function } static void on_name_acquired(GDBusConnection *connection, const gchar *name, gpointer not_used) { b->dbus_connection = connection; b->skeleton = print_backend_skeleton_new(); connect_to_signals(); connect_to_dbus(b, CPDB_BACKEND_OBJ_PATH); } static gboolean on_handle_get_printer_list(PrintBackend *interface, GDBusMethodInvocation *invocation, gpointer user_data) { int num_printers; GHashTableIter iter; gpointer key, value; GVariantBuilder builder; GVariant *printer, *printers; cups_dest_t *dest; gboolean accepting_jobs; const char *state; char *name, *info, *location, *make; GHashTable *table = cups_get_all_printers(); const char *dialog_name = g_dbus_method_invocation_get_sender(invocation); add_frontend(b, dialog_name); num_printers = g_hash_table_size(table); if (num_printers == 0) { printers = g_variant_new_array(G_VARIANT_TYPE ("(v)"), NULL, 0); print_backend_complete_get_printer_list(interface, invocation, 0, printers); return TRUE; } g_hash_table_iter_init(&iter, table); g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); while (g_hash_table_iter_next(&iter, &key, &value)) { name = key; dest = value; loginfo("Found printer : %s\n", name); info = cups_retrieve_string(dest, "printer-info"); location = cups_retrieve_string(dest, "printer-location"); make = cups_retrieve_string(dest, "printer-make-and-model"); accepting_jobs = cups_is_accepting_jobs(dest); state = cups_printer_state(dest); add_printer_to_dialog(b, dialog_name, dest); printer = g_variant_new(CPDB_PRINTER_ARGS, dest->name, dest->name, info, location, make, accepting_jobs, state, BACKEND_NAME); g_variant_builder_add(&builder, "(v)", printer); free(key); cupsFreeDests(1, value); free(info); free(location); free(make); } g_hash_table_destroy(table); printers = g_variant_builder_end(&builder); print_backend_complete_get_printer_list(interface, invocation, num_printers, printers); return TRUE; } static gboolean on_handle_get_all_translations(PrintBackend *interface, GDBusMethodInvocation *invocation, const gchar *printer_name, const gchar *locale, gpointer user_data) { PrinterCUPS *p; GVariant *translations; const char *dialog_name; dialog_name = g_dbus_method_invocation_get_sender(invocation); p = get_printer_by_name(b, dialog_name, printer_name); translations = get_printer_translations(p, locale); print_backend_complete_get_all_translations(interface, invocation, translations); return TRUE; } gpointer list_printers(gpointer _dialog_name) { char *dialog_name = (char *)_dialog_name; g_message("New thread for dialog at %s\n", dialog_name); int *cancel = get_dialog_cancel(b, dialog_name); cupsEnumDests(CUPS_DEST_FLAGS_NONE, -1, //NO timeout cancel, 0, //TYPE 0, //MASK send_printer_added, _dialog_name); g_message("Exiting thread for dialog at %s\n", dialog_name); return NULL; } int send_printer_added(void *_dialog_name, unsigned flags, cups_dest_t *dest) { char *dialog_name = (char *)_dialog_name; char *printer_name = dest->name; if (dialog_contains_printer(b, dialog_name, printer_name)) { g_message("%s already sent.\n", printer_name); return 1; } add_printer_to_dialog(b, dialog_name, dest); send_printer_added_signal(b, dialog_name, dest); g_message(" Sent notification for printer %s\n", printer_name); /** dest will be automatically freed later. * Don't explicitly free it */ return 1; //continue enumeration } static void on_stop_backend(GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer not_used) { g_message("Stop backend signal from %s\n", sender_name); /** * Ignore this signal if the dialog's keep_alive variable is set */ Dialog *d = find_dialog(b, sender_name); if (d->keep_alive) return; set_dialog_cancel(b, sender_name); remove_frontend(b, sender_name); if (no_frontends(b)) { g_message("No frontends connected .. exiting backend.\n"); exit(EXIT_SUCCESS); } } static void on_hide_remote_printers(GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer not_used) { char *dialog_name = cpdbGetStringCopy(sender_name); g_message("%s signal from %s\n", CPDB_SIGNAL_HIDE_REMOTE, dialog_name); if (!get_hide_remote(b, dialog_name)) { set_dialog_cancel(b, dialog_name); set_hide_remote_printers(b, dialog_name); refresh_printer_list(b, dialog_name); } } static void on_unhide_remote_printers(GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer not_used) { char *dialog_name = cpdbGetStringCopy(sender_name); g_message("%s signal from %s\n", CPDB_SIGNAL_UNHIDE_REMOTE, dialog_name); if (get_hide_remote(b, dialog_name)) { set_dialog_cancel(b, dialog_name); unset_hide_remote_printers(b, dialog_name); refresh_printer_list(b, dialog_name); } } static void on_hide_temp_printers(GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer not_used) { char *dialog_name = cpdbGetStringCopy(sender_name); g_message("%s signal from %s\n", CPDB_SIGNAL_HIDE_TEMP, dialog_name); if (!get_hide_temp(b, dialog_name)) { set_dialog_cancel(b, dialog_name); set_hide_temp_printers(b, dialog_name); refresh_printer_list(b, dialog_name); } } static void on_unhide_temp_printers(GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer not_used) { char *dialog_name = cpdbGetStringCopy(sender_name); g_message("%s signal from %s\n", CPDB_SIGNAL_UNHIDE_TEMP, dialog_name); if (get_hide_temp(b, dialog_name)) { set_dialog_cancel(b, dialog_name); unset_hide_temp_printers(b, dialog_name); refresh_printer_list(b, dialog_name); } } static gboolean on_handle_is_accepting_jobs(PrintBackend *interface, GDBusMethodInvocation *invocation, const gchar *printer_name, gpointer user_data) { const char *dialog_name = g_dbus_method_invocation_get_sender(invocation); cups_dest_t *dest = get_dest_by_name(b, dialog_name, printer_name); g_assert_nonnull(dest); print_backend_complete_is_accepting_jobs(interface, invocation, cups_is_accepting_jobs(dest)); return TRUE; } static gboolean on_handle_get_printer_state(PrintBackend *interface, GDBusMethodInvocation *invocation, const gchar *printer_name, gpointer user_data) { const char *dialog_name = g_dbus_method_invocation_get_sender(invocation); /// potential risk PrinterCUPS *p = get_printer_by_name(b, dialog_name, printer_name); const char *state = get_printer_state(p); printf("%s is %s\n", printer_name, state); print_backend_complete_get_printer_state(interface, invocation, state); return TRUE; } static gboolean on_handle_get_option_translation(PrintBackend *interface, GDBusMethodInvocation *invocation, const gchar *printer_name, const gchar *option_name, const gchar *locale, gpointer user_data) { const char *dialog_name = g_dbus_method_invocation_get_sender(invocation); /// potential risk PrinterCUPS *p = get_printer_by_name(b, dialog_name, printer_name); char *translation = get_option_translation(p, option_name, locale); if (translation == NULL) translation = cpdbGetStringCopy(option_name); print_backend_complete_get_option_translation(interface, invocation, translation); return TRUE; } static gboolean on_handle_get_choice_translation(PrintBackend *interface, GDBusMethodInvocation *invocation, const gchar *printer_name, const gchar *option_name, const gchar *choice_name, const gchar *locale, gpointer user_data) { const char *dialog_name = g_dbus_method_invocation_get_sender(invocation); /// potential risk PrinterCUPS *p = get_printer_by_name(b, dialog_name, printer_name); char *translation = get_choice_translation(p, option_name, choice_name, locale); if (translation == NULL) translation = cpdbGetStringCopy(choice_name); print_backend_complete_get_choice_translation(interface, invocation, translation); return TRUE; } static gboolean on_handle_get_group_translation(PrintBackend *interface, GDBusMethodInvocation *invocation, const gchar *printer_name, const gchar *group_name, const gchar *locale, gpointer user_data) { char *translation = cpdbGetGroupTranslation2(group_name, locale); print_backend_complete_get_group_translation(interface, invocation, translation); free(translation); return TRUE; } static gboolean on_handle_ping(PrintBackend *interface, GDBusMethodInvocation *invocation, const gchar *printer_name, gpointer user_data) { const char *dialog_name = g_dbus_method_invocation_get_sender(invocation); /// potential risk PrinterCUPS *p = get_printer_by_name(b, dialog_name, printer_name); print_backend_complete_ping(interface, invocation); tryPPD(p); return TRUE; } static gboolean on_handle_print_file(PrintBackend *interface, GDBusMethodInvocation *invocation, const gchar *printer_name, const gchar *file_path, int num_settings, GVariant *settings, const gchar *final_file_path, gpointer user_data) { const char *dialog_name = g_dbus_method_invocation_get_sender(invocation); /// potential risk PrinterCUPS *p = get_printer_by_name(b, dialog_name, printer_name); int job_id = print_file(p, file_path, num_settings, settings); char jobid_string[64]; snprintf(jobid_string, sizeof(jobid_string), "%d", job_id); print_backend_complete_print_file(interface, invocation, jobid_string); /** * (Currently Disabled) Printing will always be the last operation, so remove that frontend */ //set_dialog_cancel(b, dialog_name); //remove_frontend(b, dialog_name); if (no_frontends(b)) { g_message("No frontends connected .. exiting backend.\n"); exit(EXIT_SUCCESS); } return TRUE; } static gboolean on_handle_get_all_options(PrintBackend *interface, GDBusMethodInvocation *invocation, const gchar *printer_name, gpointer user_data) { const char *dialog_name = g_dbus_method_invocation_get_sender(invocation); /// potential risk PrinterCUPS *p = get_printer_by_name(b, dialog_name, printer_name); Media *medias; int media_count = get_all_media(p, &medias); GVariantBuilder *builder; GVariant *media_variant; builder = g_variant_builder_new(G_VARIANT_TYPE("a(siiia(iiii))")); for (int i = 0; i < media_count; i++) { GVariant *tuple = pack_media(&medias[i]); g_variant_builder_add_value(builder, tuple); } media_variant = g_variant_builder_end(builder); Option *options; int count = get_all_options(p, &options); count = add_media_to_options(p, medias, media_count, &options, count); GVariant *variant; builder = g_variant_builder_new(G_VARIANT_TYPE("a(sssia(s))")); for (int i = 0; i < count; i++) { GVariant *tuple = pack_option(&options[i]); g_variant_builder_add_value(builder, tuple); //g_variant_unref(tuple); } variant = g_variant_builder_end(builder); print_backend_complete_get_all_options(interface, invocation, count, variant, media_count, media_variant); free_options(count, options); return TRUE; } static gboolean on_handle_get_active_jobs_count(PrintBackend *interface, GDBusMethodInvocation *invocation, const gchar *printer_name, gpointer user_data) { const char *dialog_name = g_dbus_method_invocation_get_sender(invocation); /// potential risk PrinterCUPS *p = get_printer_by_name(b, dialog_name, printer_name); print_backend_complete_get_active_jobs_count(interface, invocation, get_active_jobs_count(p)); return TRUE; } static gboolean on_handle_get_all_jobs(PrintBackend *interface, GDBusMethodInvocation *invocation, gboolean active_only, gpointer user_data) { const char *dialog_name = g_dbus_method_invocation_get_sender(invocation); /// potential risk int n; GVariant *variant = get_all_jobs(b, dialog_name, &n, active_only); print_backend_complete_get_all_jobs(interface, invocation, n, variant); return TRUE; } static gboolean on_handle_cancel_job(PrintBackend *interface, GDBusMethodInvocation *invocation, const gchar *job_id, const gchar *printer_name, gpointer user_data) { const char *dialog_name = g_dbus_method_invocation_get_sender(invocation); int jobid = atoi(job_id); /**to do. check if given job id is integer */ PrinterCUPS *p = get_printer_by_name(b, dialog_name, printer_name); gboolean status = cancel_job(p, jobid); print_backend_complete_cancel_job(interface, invocation, status); return TRUE; } static gboolean on_handle_get_default_printer(PrintBackend *interface, GDBusMethodInvocation *invocation, gpointer user_data) { char *def = get_default_printer(b); printf("%s\n", def); print_backend_complete_get_default_printer(interface, invocation, def); return TRUE; } static gboolean on_handle_keep_alive(PrintBackend *interface, GDBusMethodInvocation *invocation, gpointer user_data) { const char *dialog_name = g_dbus_method_invocation_get_sender(invocation); Dialog *d = find_dialog(b, dialog_name); d->keep_alive = TRUE; print_backend_complete_keep_alive(interface, invocation); return TRUE; } static gboolean on_handle_replace(PrintBackend *interface, GDBusMethodInvocation *invocation, const gchar *previous_name, gpointer user_data) { const char *dialog_name = g_dbus_method_invocation_get_sender(invocation); Dialog *d = find_dialog(b, previous_name); if (d != NULL) { g_hash_table_steal(b->dialogs, previous_name); g_hash_table_insert(b->dialogs, cpdbGetStringCopy(dialog_name), d); g_message("Replaced %s --> %s\n", previous_name, dialog_name); } print_backend_complete_replace(interface, invocation); return TRUE; } void connect_to_signals() { PrintBackend *skeleton = b->skeleton; g_signal_connect(skeleton, //instance "handle-get-printer-list", //signal name G_CALLBACK(on_handle_get_printer_list), //callback NULL); g_signal_connect(skeleton, //instance "handle-get-all-options", //signal name G_CALLBACK(on_handle_get_all_options), //callback NULL); g_signal_connect(skeleton, //instance "handle-ping", //signal name G_CALLBACK(on_handle_ping), //callback NULL); g_signal_connect(skeleton, //instance "handle-get-default-printer", //signal name G_CALLBACK(on_handle_get_default_printer), //callback NULL); g_signal_connect(skeleton, //instance "handle-print-file", //signal name G_CALLBACK(on_handle_print_file), //callback NULL); g_signal_connect(skeleton, //instance "handle-get-printer-state", //signal name G_CALLBACK(on_handle_get_printer_state), //callback NULL); g_signal_connect(skeleton, //instance "handle-is-accepting-jobs", //signal name G_CALLBACK(on_handle_is_accepting_jobs), //callback NULL); g_signal_connect(skeleton, //instance "handle-get-active-jobs-count", //signal name G_CALLBACK(on_handle_get_active_jobs_count), //callback NULL); g_signal_connect(skeleton, //instance "handle-get-all-jobs", //signal name G_CALLBACK(on_handle_get_all_jobs), //callback NULL); g_signal_connect(skeleton, //instance "handle-cancel-job", //signal name G_CALLBACK(on_handle_cancel_job), //callback NULL); g_signal_connect(skeleton, //instance "handle-keep-alive", //signal name G_CALLBACK(on_handle_keep_alive), //callback NULL); g_signal_connect(skeleton, //instance "handle-replace", //signal name G_CALLBACK(on_handle_replace), //callback NULL); g_signal_connect(skeleton, //instance "handle-get-option-translation", //signal name G_CALLBACK(on_handle_get_option_translation), //callback NULL); g_signal_connect(skeleton, //instance "handle-get-choice-translation", //signal name G_CALLBACK(on_handle_get_choice_translation), //callback NULL); g_signal_connect(skeleton, //instance "handle-get-group-translation", //signal name G_CALLBACK(on_handle_get_group_translation), //callback NULL); g_signal_connect(skeleton, "handle-get-all-translations", G_CALLBACK(on_handle_get_all_translations), NULL); g_dbus_connection_signal_subscribe(b->dbus_connection, NULL, //Sender name "org.openprinting.PrintFrontend", //Sender interface CPDB_SIGNAL_STOP_BACKEND, //Signal name NULL, /**match on all object paths**/ NULL, /**match on all arguments**/ 0, //Flags on_stop_backend, //callback NULL, //user_data NULL); g_dbus_connection_signal_subscribe(b->dbus_connection, NULL, //Sender name "org.openprinting.PrintFrontend", //Sender interface CPDB_SIGNAL_HIDE_REMOTE, //Signal name NULL, /**match on all object paths**/ NULL, /**match on all arguments**/ 0, //Flags on_hide_remote_printers, //callback NULL, //user_data NULL); g_dbus_connection_signal_subscribe(b->dbus_connection, NULL, //Sender name "org.openprinting.PrintFrontend", //Sender interface CPDB_SIGNAL_UNHIDE_REMOTE, //Signal name NULL, /**match on all object paths**/ NULL, /**match on all arguments**/ 0, //Flags on_unhide_remote_printers, //callback NULL, //user_data NULL); g_dbus_connection_signal_subscribe(b->dbus_connection, NULL, //Sender name "org.openprinting.PrintFrontend", //Sender interface CPDB_SIGNAL_HIDE_TEMP, //Signal name NULL, /**match on all object paths**/ NULL, /**match on all arguments**/ 0, //Flags on_hide_temp_printers, //callback NULL, //user_data NULL); g_dbus_connection_signal_subscribe(b->dbus_connection, NULL, //Sender name "org.openprinting.PrintFrontend", //Sender interface CPDB_SIGNAL_UNHIDE_TEMP, //Signal name NULL, /**match on all object paths**/ NULL, /**match on all arguments**/ 0, //Flags on_unhide_temp_printers, //callback NULL, //user_data NULL); } cpdb-backend-cups-2.0b5/src/run-tests.sh000077500000000000000000000362571446254674500201770ustar00rootroot00000000000000#!/bin/sh # # Perform testing of automatic print queue generation and removal by # cups-browsed # # Copyright © 2020-2023 by OpenPrinting # Copyright © 2007-2021 by Apple Inc. # Copyright © 1997-2007 by Easy Software Products, all rights reserved. # # Licensed under Apache License v2.0. See the file "LICENSE" for more # information. # # # We use "dbus-run-session" to have our own, private D-Bus session bus, # to avoid any interaction with the host system. # if ! test "${1}" = "d"; then exec dbus-run-session -- ${0} d fi # # Clean up after the tests, and preserve logs for failed "make check" # Shut down daemons, remove test bed if we have created one # clean_up() { #return # # Shut down all the daemons # kill_sent=0 if (test "x$BACKEND_PID" != "x"); then kill -TERM $BACKEND_PID 2>/dev/null kill_sent=1 fi if (test "x$FRONTEND_PID" != "x"); then kill -TERM $FRONTEND_PID 2>/dev/null kill_sent=1 fi if (test "x$cupsd" != "x"); then kill -TERM $cupsd 2>/dev/null kill_sent=1 fi if test $kill_sent = 1; then sleep 5 fi # # Hard-kill any remaining daemon # if (test "x$BACKEND_PID" != "x"); then kill -KILL $BACKEND_PID 2>/dev/null fi if (test "x$FRONTEND_PID" != "x"); then kill -KILL $FRONTEND_PID 2>/dev/null fi if (test "x$cupsd" != "x"); then kill -KILL $cupsd 2>/dev/null fi # # Preserve logs in case of failure # if test "x$RES" != "x0"; then if test -n "$BASE"; then echo "============================" echo "CPDB FRONTEND LOG" echo "============================" echo "" cat $BASE/log/frontend_log echo "" echo "" echo "============================" echo "CPDB BACKEND LOG" echo "============================" echo "" cat $BASE/log/backend_log echo "" echo "" echo "============================" echo "CUPS ERROR_LOG" echo "============================" echo "" cat $BASE/log/error_log echo "" echo "" echo "============================" echo "CUPS ACCESS_LOG" echo "============================" echo "" cat $BASE/log/access_log echo "" echo "" echo "============================" echo "CUPSD DEBUG LOG" echo "============================" echo "" cat $BASE/log/cupsd_debug_log echo "" echo "" fi fi # # Remove test bed directories # if test -n "$BASE"; then rm -rf $BASE fi } # # Call clean_up() whenever this script gets interrupted, both by signals # end by errors... # RES=1 trap clean_up 0 EXIT INT QUIT ABRT PIPE TERM # # Force the permissions of the files we create... # umask 022 # # Solaris has a non-POSIX grep in /bin... # if test -x /usr/xpg4/bin/grep; then GREP=/usr/xpg4/bin/grep else GREP=grep fi # # Figure out the proper echo options... # if (echo "testing\c"; echo 1,2,3) | $GREP c >/dev/null; then ac_n=-n ac_c= else ac_n= ac_c='\c' fi # # CUPS resource directories of the system # # For "make check" testing we copy/link our testbed CUPS # components from there # sys_datadir=`cups-config --datadir` sys_serverbin=`cups-config --serverbin` sys_serverroot=`cups-config --serverroot` # # Information for the server/tests... # echo "Running 'make check' ..." echo "" echo "Running own CUPS instance on alternative port" echo "Using CPDB CUPS backend from source tree" port="${CUPS_TESTPORT:=8631}" CUPS_TESTROOT=.; export CUPS_TESTROOT BASE="${CUPS_TESTBASE:=}" if test -z "$BASE"; then if test -d /private/tmp; then BASE=/private/tmp/cpdb-backend-cups-$user else BASE=/tmp/cpdb-backend-cups-$USER fi fi export BASE # # Make sure that the LPDEST and PRINTER environment variables are # not included in the environment that is passed to the tests. These # will usually cause tests to fail erroneously... # unset LPDEST unset PRINTER # # Start by creating temporary directories for the tests... # echo "Creating directories for test..." rm -rf $BASE mkdir $BASE mkdir $BASE/bin mkdir $BASE/bin/backend mkdir $BASE/bin/driver mkdir $BASE/bin/filter mkdir $BASE/cache mkdir $BASE/certs mkdir $BASE/share mkdir $BASE/share/banners mkdir $BASE/share/drv mkdir $BASE/share/locale for file in $sys_datadir/locale/*/cups_*.po; do loc=`basename $file .po | cut -c 6-` mkdir $BASE/share/locale/$loc ln -s $file $BASE/share/locale/$loc done mkdir $BASE/share/data mkdir $BASE/share/mime mkdir $BASE/share/model mkdir $BASE/share/ppdc mkdir $BASE/interfaces mkdir $BASE/log mkdir $BASE/ppd mkdir $BASE/spool mkdir $BASE/spool/temp mkdir $BASE/ssl # # We copy the cupsd executable to break it off from the Debian/Ubuntu # package's AppArmor capsule, so that it can work with our test bed # directories # cp /usr/sbin/cupsd $BASE/bin/ ln -s $sys_serverbin/backend/dnssd $BASE/bin/backend ln -s $sys_serverbin/backend/http $BASE/bin/backend ln -s $sys_serverbin/backend/ipp $BASE/bin/backend ln -s ipp $BASE/bin/backend/ipps ln -s $sys_serverbin/backend/lpd $BASE/bin/backend ln -s $sys_serverbin/backend/mdns $BASE/bin/backend ln -s $sys_serverbin/backend/snmp $BASE/bin/backend ln -s $sys_serverbin/backend/socket $BASE/bin/backend ln -s $sys_serverbin/backend/usb $BASE/bin/backend ln -s $sys_serverbin/cgi-bin $BASE/bin ln -s $sys_serverbin/monitor $BASE/bin ln -s $sys_serverbin/notifier $BASE/bin ln -s $sys_serverbin/daemon $BASE/bin ln -s $sys_serverbin/filter/commandtops $BASE/bin/filter ln -s $sys_serverbin/filter/gziptoany $BASE/bin/filter ln -s $sys_serverbin/filter/pstops $BASE/bin/filter ln -s $sys_serverbin/filter/rastertoepson $BASE/bin/filter ln -s $sys_serverbin/filter/rastertohp $BASE/bin/filter ln -s $sys_serverbin/filter/rastertolabel $BASE/bin/filter ln -s $sys_serverbin/filter/rastertopwg $BASE/bin/filter cat >$BASE/share/banners/standard <$BASE/share/banners/classified <$BASE/cupsd.conf < Order Allow,Deny WebInterface yes EOF cat >$BASE/cups-files.conf <>$BASE/printers.conf < Accepting Yes DeviceURI file:/dev/null Info Test PS printer $i JobSheets none none Location CUPS test suite State Idle StateMessage Printer $1 is idle. EOF cp testpdf.ppd $BASE/ppd/printer-$i.ppd i=`expr $i + 1` done if test -f $BASE/printers.conf; then cp $BASE/printers.conf $BASE/printers.conf.orig else touch $BASE/printers.conf.orig fi # # Create a helper script to run programs with... # echo "Setting up environment variables for test..." if test "x$ASAN_OPTIONS" = x; then # AddressSanitizer on Linux reports memory leaks from the main function # which is basically useless - in general, programs do not need to free # every object before exit since the OS will recover the process's # memory. ASAN_OPTIONS="detect_leaks=false" export ASAN_OPTIONS fi # These get exported because they don't have side-effects... CUPS_DISABLE_APPLE_DEFAULT=yes; export CUPS_DISABLE_APPLE_DEFAULT CUPS_SERVER=localhost:$port; export CUPS_SERVER CUPS_SERVERROOT=$BASE; export CUPS_SERVERROOT CUPS_STATEDIR=$BASE; export CUPS_STATEDIR CUPS_DATADIR=$BASE/share; export CUPS_DATADIR IPP_PORT=$port; export IPP_PORT LOCALEDIR=$BASE/share/locale; export LOCALEDIR echo "Creating wrapper script..." runcups="$BASE/runcups"; export runcups echo "#!/bin/sh" >$runcups echo "# Helper script for running CUPS test instance." >>$runcups echo "" >>$runcups echo "# Set required environment variables..." >>$runcups echo "CUPS_DATADIR=\"$CUPS_DATADIR\"; export CUPS_DATADIR" >>$runcups echo "CUPS_SERVER=\"$CUPS_SERVER\"; export CUPS_SERVER" >>$runcups echo "CUPS_SERVERROOT=\"$CUPS_SERVERROOT\"; export CUPS_SERVERROOT" >>$runcups echo "CUPS_STATEDIR=\"$CUPS_STATEDIR\"; export CUPS_STATEDIR" >>$runcups echo "DYLD_INSERT_LIBRARIES=\"$DYLD_INSERT_LIBRARIES\"; export DYLD_INSERT_LIBRARIES" >>$runcups echo "DYLD_LIBRARY_PATH=\"$DYLD_LIBRARY_PATH\"; export DYLD_LIBRARY_PATH" >>$runcups # IPP_PORT=$port; export IPP_PORT echo "LD_LIBRARY_PATH=\"$LD_LIBRARY_PATH\"; export LD_LIBRARY_PATH" >>$runcups echo "LD_PRELOAD=\"$LD_PRELOAD\"; export LD_PRELOAD" >>$runcups echo "LOCALEDIR=\"$LOCALEDIR\"; export LOCALEDIR" >>$runcups if test "x$CUPS_DEBUG_LEVEL" != x; then echo "CUPS_DEBUG_FILTER='$CUPS_DEBUG_FILTER'; export CUPS_DEBUG_FILTER" >>$runcups echo "CUPS_DEBUG_LEVEL=$CUPS_DEBUG_LEVEL; export CUPS_DEBUG_LEVEL" >>$runcups echo "CUPS_DEBUG_LOG='$CUPS_DEBUG_LOG'; export CUPS_DEBUG_LOG" >>$runcups fi echo "" >>$runcups echo "# Run command..." >>$runcups echo "exec \"\$@\"" >>$runcups chmod +x $runcups # # Set a new home directory to avoid getting user options mixed in and for CPDB # not complain about missing directory for saving configuration... # HOME=$BASE export HOME XDG_CONFIG_HOME=${BASE}/.config export XDG_CONFIG_HOME mkdir ${BASE}/.config # # Force POSIX locale for tests... # LANG=C export LANG LC_MESSAGES=C export LC_MESSAGES # # Start the CUPS server; run as foreground daemon in the background... # echo "Starting cupsd:" echo " $runcups $BASE/bin/cupsd -c $BASE/cupsd.conf -f >$BASE/log/cupsd_debug_log 2>&1 &" echo "" $runcups $BASE/bin/cupsd -c $BASE/cupsd.conf -f >$BASE/log/cupsd_debug_log 2>&1 & cupsd=$! if (test "x$cupsd" != "x"); then echo "cupsd is PID $cupsd." echo "" fi sleep 2 # # Fire up the CPDB CUPS backend # BACKEND=./cups BACKEND_INFO_SOURCE_DIR=../data CPDB_BACKEND_INFO_DIR=$BASE BACKEND_INFO=org.openprinting.Backend.CUPS # Set backend info directory and copy the info file to there. # In its original place, in the data/ sub-directory it is together # with the systemd service file which gets confused as a backend info # file by cpdb-libs, therefore we copy it. export CPDB_BACKEND_INFO_DIR cp $BACKEND_INFO_SOURCE_DIR/$BACKEND_INFO $CPDB_BACKEND_INFO_DIR # Create the log file BACKEND_LOG=$BASE/log/backend_log rm -f $BACKEND_LOG touch $BACKEND_LOG echo "Starting CPDB CUPS backend:" echo " $runcups $BACKEND >$BACKEND_LOG 2>&1 &" echo "" $runcups $BACKEND >$BACKEND_LOG 2>&1 & BACKEND_PID=$! if (test "x$BACKEND_PID" != "x"); then echo "CPDB CUPS backend is PID $BACKEND_PID." echo "" fi # # Run the test frontend and feed in commands. # FRONTEND=cpdb-text-frontend QUEUE=printer-1 FILE_TO_PRINT=/usr/share/cups/data/default-testpage.pdf # Set default page size to Envelope #10 $runcups lpadmin -p $QUEUE -o PageSize=Env10 # Disable queue to hold output file in spool directory $runcups cupsdisable $QUEUE # Create the log file FRONTEND_LOG=$BASE/log/frontend_log rm -f $FRONTEND_LOG touch $FRONTEND_LOG echo "Starting CPDB CUPS frontend:" echo " $FRONTEND > $FRONTEND_LOG 2>&1 &" echo "" ( \ sleep 5; \ echo get-all-options $QUEUE CUPS; \ sleep 2; \ echo print-file $FILE_TO_PRINT $QUEUE CUPS; \ sleep 1; \ echo stop \ ) | $FRONTEND > $FRONTEND_LOG 2>&1 & FRONTEND_PID=$! if (test "x$FRONTEND_PID" != "x"); then echo "CPDB frontend is PID $FRONTEND_PID." echo "" fi # # Give the frontend a maximum of 12 seconds to run and then kill it, to avoid # the script getting stuck if stopping the frontend fails. # i=0 while kill -0 $FRONTEND_PID >/dev/null 2>&1; do i=$((i+1)) if test $i -ge 12; then kill -KILL $FRONTEND_PID >/dev/null 2>&1 || true FRONTEND_PID= echo "FAIL: Frontend keeps running!" exit 1 fi sleep 1 done FRONTEND_PID= # # Stop the backend # if kill -0 $BACKEND_PID >/dev/null 2>&1; then kill -TERM $BACKEND_PID # Give the backend a maximum of 3 seconds to shut down and then kill it, # to avoid the script getting stuck if shutdown fails. i=0 while kill -0 $BACKEND_PID >/dev/null 2>&1; do i=$((i+1)) if test $i -ge 3; then kill -KILL $BACKEND_PID >/dev/null 2>&1 || true BACKEND_PID= echo "FAIL: Backend did not shut down!" exit 1 fi sleep 1 done fi BACKEND_PID= # # Check log for test results # # Does the printer appear in the initial list of available printers? echo "Initial listing of the printer:" if ! grep '^Printer '$QUEUE'$' $FRONTEND_LOG; then echo "FAIL: CUPS queue $QUEUE not listed!" exit 1 fi echo # Does the attribute "printer-resolution" appear in the list of options? echo "Attribute listing of \"printer-resolution\":" if ! grep 'printer-resolution' $FRONTEND_LOG; then echo "FAIL: Attribute \"printer-resolution\" not listed!" exit 1 fi echo # Does the attribute "print-color-mode" appear in the list of options? echo "Attribute listing of \"print-color-mode\":" if ! grep 'print-color-mode' $FRONTEND_LOG; then echo "FAIL: Attribute \"print-color-mode\" not listed!" exit 1 fi echo # Does the setting "na_number-10_4.125x9.5in" appear as a default setting? echo "\"na_number-10_4.125x9.5in\" as a default setting:" if ! grep 'DEFAULT: *na_number-10_4.125x9.5in' $FRONTEND_LOG; then echo "FAIL: Setting \"na_number-10_4.125x9.5in\" not listed as default!" exit 1 fi echo # Did the successful submission of a print job get confirmed? echo "Confirmation message for job submission:" if ! grep -i 'Document send succeeded' $BACKEND_LOG; then echo "FAIL: No confirmation of job submission!" exit 1 fi echo # # Tests successfully completed # echo "SUCCESS: All tests were successful." echo "" RES=0 exit 0 cpdb-backend-cups-2.0b5/src/test.convs000066400000000000000000000001431446254674500177060ustar00rootroot00000000000000# pdftopdf filter of cups-filters 1.x or 2.x application/pdf application/vnd.cups-pdf 100 pdftopdf cpdb-backend-cups-2.0b5/src/testpdf.ppd000066400000000000000000000354161446254674500200460ustar00rootroot00000000000000*PPD-Adobe: "4.3" *% *% This PPD file is for using a PDF printer in its native PDF mode, with *% option settings controlled via PJL commands. It uses CUPS with the *% OpenPrinting cups-filters package. *% *% This PPD is experimental. It is possible that some of the options *% and settings do not make sense or do not cause any change on the output. *% It can even happen that with certain settings nothing or an error page *% comes out. *% *% Licensed under Apache License v2.0. See the file "LICENSE" for more *% information. *% *% You may save this file as 'Generic-PDF_Printer-PDF.ppd' *% *% *FormatVersion: "4.3" *FileVersion: "1.1" *LanguageVersion: English *LanguageEncoding: ISOLatin1 *PCFileName: "PDF.PPD" *Manufacturer: "Generic" *Product: "(Generic PDF Printer)" *cupsVersion: 1.0 *cupsManualCopies: True *cupsModelNumber: 2 *ModelName: "Generic PDF Printer" *ShortNickName: "Generic PDF Printer" *NickName: "Generic PDF Printer" *PSVersion: "(3010.107) 0" *LanguageLevel: "3" *ColorDevice: True *DefaultColorSpace: CMYK *FileSystem: False *Throughput: "30" *LandscapeOrientation: Plus90 *TTRasterizer: Type42 *1284DeviceID: "MFG:Generic;CMD:PJL,PDF;MDL:PDF Printer;CLS:PRINTER;DES:Generic PDF Printer;DRV:DPDF,R1,M0;" *JCLBegin: "<1B>%-12345X@PJL JOB<0A>" *JCLToPDFInterpreter: "@PJL ENTER LANGUAGE = PDF<0A>" *JCLEnd: "<1B>%-12345X@PJL EOJ <0A><1B>%-12345X" *cupsFilter: "application/vnd.cups-pdf 0 -" *OpenGroup: General/General *JCLOpenUI *PageSize/Page Size: PickOne *OrderDependency: 100 JCLSetup *PageSize *DefaultPageSize: Letter *PageSize Letter/Letter: "@PJL SET PAPER=LETTER<0A>" *PageSize Letter.Fullbleed/Letter (Borderless): "@PJL SET PAPER=LETTER<0A>" *PageSize A4/A4: "@PJL SET PAPER=A4<0A>" *PageSize A4.Fullbleed/A4 (Borderless): "@PJL SET PAPER=A4<0A>" *PageSize A5/A5: "@PJL SET PAPER=A5<0A>" *PageSize A5.Fullbleed/A5 (Borderless): "@PJL SET PAPER=A5<0A>" *PageSize A6/A6: "@PJL SET PAPER=A6<0A>" *PageSize A6.Fullbleed/A6 (Borderless): "@PJL SET PAPER=A6<0A>" *PageSize ISOB5/ISO B5: "@PJL SET PAPER=B5<0A>" *PageSize ISOB5.Fullbleed/ISO B5 (Borderless): "@PJL SET PAPER=B5<0A>" *PageSize EnvC5/C5: "@PJL SET PAPER=C5<0A>" *PageSize EnvC5.Fullbleed/C5 (Borderless): "@PJL SET PAPER=C5<0A>" *PageSize Env10/Com 10: "@PJL SET PAPER=COM10<0A>" *PageSize Env10.Fullbleed/Com 10 (Borderless): "@PJL SET PAPER=COM10<0A>" *PageSize EnvDL/DL: "@PJL SET PAPER=DL<0A>" *PageSize EnvDL.Fullbleed/DL (Borderless): "@PJL SET PAPER=DL<0A>" *PageSize 5x13/Eight Point 5x13: "@PJL SET PAPER=EIGHTPOINT5X13<0A>" *PageSize 5x13.Fullbleed/Eight Point 5x13 (Borderless): "@PJL SET PAPER=EIGHTPOINT5X13<0A>" *PageSize EnvC6/Envelope C6: "@PJL SET PAPER=ENVELOPEC6<0A>" *PageSize EnvC6.Fullbleed/Envelope C6 (Borderless): "@PJL SET PAPER=ENVELOPEC6<0A>" *PageSize Executive/Executive: "@PJL SET PAPER=EXECUTIVE<0A>" *PageSize Executive.Fullbleed/Executive (Borderless): "@PJL SET PAPER=EXECUTIVE<0A>" *PageSize B5/JIS B5: "@PJL SET PAPER=JISB5<0A>" *PageSize B5.Fullbleed/JIS B5 (Borderless): "@PJL SET PAPER=JISB5<0A>" *PageSize B6/JIS B6: "@PJL SET PAPER=JISB6<0A>" *PageSize B6.Fullbleed/JIS B6 (Borderless): "@PJL SET PAPER=JISB6<0A>" *PageSize Legal/Legal: "@PJL SET PAPER=LEGAL<0A>" *PageSize Legal.Fullbleed/Legal (Borderless): "@PJL SET PAPER=LEGAL<0A>" *PageSize EnvMonarch/Monarch: "@PJL SET PAPER=MONARCH<0A>" *PageSize EnvMonarch.Fullbleed/Monarch (Borderless): "@PJL SET PAPER=MONARCH<0A>" *PageSize 69x95mm/16K: "@PJL SET PAPER=SIZE16K195x270<0A>" *PageSize 69x95mm.Fullbleed/16K (Borderless): "@PJL SET PAPER=SIZE16K195x270<0A>" *PageSize Statement/Statement: "@PJL SET PAPER=STATEMENT<0A>" *PageSize Statement.Fullbleed/Statement (Borderless): "@PJL SET PAPER=STATEMENT<0A>" *JCLCloseUI: *PageSize *JCLOpenUI *PageRegion: PickOne *OrderDependency: 100 JCLSetup *PageRegion *DefaultPageRegion: Letter *PageRegion Letter/Letter: "@PJL SET PAPER=LETTER<0A>" *PageRegion Letter.Fullbleed/Letter (Borderless): "@PJL SET PAPER=LETTER<0A>" *PageRegion A4/A4: "@PJL SET PAPER=A4<0A>" *PageRegion A4.Fullbleed/A4 (Borderless): "@PJL SET PAPER=A4<0A>" *PageRegion A5/A5: "@PJL SET PAPER=A5<0A>" *PageRegion A5.Fullbleed/A5 (Borderless): "@PJL SET PAPER=A5<0A>" *PageRegion A6/A6: "@PJL SET PAPER=A6<0A>" *PageRegion A6.Fullbleed/A6 (Borderless): "@PJL SET PAPER=A6<0A>" *PageRegion ISOB5/ISO B5: "@PJL SET PAPER=B5<0A>" *PageRegion ISOB5.Fullbleed/ISO B5 (Borderless): "@PJL SET PAPER=B5<0A>" *PageRegion EnvC5/C5: "@PJL SET PAPER=C5<0A>" *PageRegion EnvC5.Fullbleed/C5 (Borderless): "@PJL SET PAPER=C5<0A>" *PageRegion Env10/Com 10: "@PJL SET PAPER=COM10<0A>" *PageRegion Env10.Fullbleed/Com 10 (Borderless): "@PJL SET PAPER=COM10<0A>" *PageRegion EnvDL/DL: "@PJL SET PAPER=DL<0A>" *PageRegion EnvDL.Fullbleed/DL (Borderless): "@PJL SET PAPER=DL<0A>" *PageRegion 5x13/Eight Point 5x13: "@PJL SET PAPER=EIGHTPOINT5X13<0A>" *PageRegion 5x13.Fullbleed/Eight Point 5x13 (Borderless): "@PJL SET PAPER=EIGHTPOINT5X13<0A>" *PageRegion EnvC6/Envelope C6: "@PJL SET PAPER=ENVELOPEC6<0A>" *PageRegion EnvC6.Fullbleed/Envelope C6 (Borderless): "@PJL SET PAPER=ENVELOPEC6<0A>" *PageRegion Executive/Executive: "@PJL SET PAPER=EXECUTIVE<0A>" *PageRegion Executive.Fullbleed/Executive (Borderless): "@PJL SET PAPER=EXECUTIVE<0A>" *PageRegion B5/JIS B5: "@PJL SET PAPER=JISB5<0A>" *PageRegion B5.Fullbleed/JIS B5 (Borderless): "@PJL SET PAPER=JISB5<0A>" *PageRegion B6/JIS B6: "@PJL SET PAPER=JISB6<0A>" *PageRegion B6.Fullbleed/JIS B6 (Borderless): "@PJL SET PAPER=JISB6<0A>" *PageRegion Legal/Legal: "@PJL SET PAPER=LEGAL<0A>" *PageRegion Legal.Fullbleed/Legal (Borderless): "@PJL SET PAPER=LEGAL<0A>" *PageRegion EnvMonarch/Monarch: "@PJL SET PAPER=MONARCH<0A>" *PageRegion EnvMonarch.Fullbleed/Monarch (Borderless): "@PJL SET PAPER=MONARCH<0A>" *PageRegion 69x95mm/16K: "@PJL SET PAPER=SIZE16K195x270<0A>" *PageRegion 69x95mm.Fullbleed/16K (Borderless): "@PJL SET PAPER=SIZE16K195x270<0A>" *PageRegion Statement/Statement: "@PJL SET PAPER=STATEMENT<0A>" *PageRegion Statement.Fullbleed/Statement (Borderless): "@PJL SET PAPER=STATEMENT<0A>" *JCLCloseUI: *PageRegion *DefaultImageableArea: Letter *ImageableArea Letter/Letter: "18 36 594 756" *ImageableArea Letter.Fullbleed/Letter (Borderless): "0 0 612 792" *ImageableArea A4/A4: "18 36 577 806" *ImageableArea A4.Fullbleed/A4 (Borderless): "0 0 595 842" *ImageableArea A5/A5: "18 36 402 559" *ImageableArea A5.Fullbleed/A5 (Borderless): "0 0 420 595" *ImageableArea A6/A6: "18 36 279 384" *ImageableArea A6.Fullbleed/A6 (Borderless): "0 0 297 420" *ImageableArea ISOB5/ISO B5: "18 36 480 672" *ImageableArea ISOB5.Fullbleed/ISO B5 (Borderless): "0 0 498 708" *ImageableArea EnvC5/C5: "18 36 441 613" *ImageableArea EnvC5.Fullbleed/C5 (Borderless): "0 0 459 649" *ImageableArea Env10/Com 10: "18 36 279 648" *ImageableArea Env10.Fullbleed/Com 10 (Borderless): "0 0 297 684" *ImageableArea EnvDL/DL: "18 36 293 587" *ImageableArea EnvDL.Fullbleed/DL (Borderless): "0 0 311 623" *ImageableArea 5x13/Eight Point 5x13: "18 36 342 900" *ImageableArea 5x13.Fullbleed/Eight Point 5x13 (Borderless): "0 0 360 936" *ImageableArea EnvC6/Envelope C6: "18 36 305 423" *ImageableArea EnvC6.Fullbleed/Envelope C6 (Borderless): "0 0 323 459" *ImageableArea Executive/Executive: "18 36 504 720" *ImageableArea Executive.Fullbleed/Executive (Borderless): "0 0 522 756" *ImageableArea B5/JIS B5: "18 36 500 691" *ImageableArea B5.Fullbleed/JIS B5 (Borderless): "0 0 518 727" *ImageableArea B6/JIS B6: "18 36 344 482" *ImageableArea B6.Fullbleed/JIS B6 (Borderless): "0 0 362 518" *ImageableArea Legal/Legal: "18 36 594 972" *ImageableArea Legal.Fullbleed/Legal (Borderless): "0 0 612 1008" *ImageableArea EnvMonarch/Monarch: "18 36 261 504" *ImageableArea EnvMonarch.Fullbleed/Monarch (Borderless): "0 0 279 540" *ImageableArea 69x95mm/16K: "18 36 177 234" *ImageableArea 69x95mm.Fullbleed/16K (Borderless): "0 0 195 270" *ImageableArea Statement/Statement: "18 36 378 576" *ImageableArea Statement.Fullbleed/Statement (Borderless): "0 0 396 612" *DefaultPaperDimension: Letter *PaperDimension Letter/Letter: "612 792" *PaperDimension Letter.Fullbleed/Letter (Borderless): "612 792" *PaperDimension A4/A4: "595 842" *PaperDimension A4.Fullbleed/A4 (Borderless): "595 842" *PaperDimension A5/A5: "420 595" *PaperDimension A5.Fullbleed/A5 (Borderless): "420 595" *PaperDimension A6/A6: "297 420" *PaperDimension A6.Fullbleed/A6 (Borderless): "297 420" *PaperDimension ISOB5/ISO B5: "498 708" *PaperDimension ISOB5.Fullbleed/ISO B5 (Borderless): "498 708" *PaperDimension EnvC5/C5: "459 649" *PaperDimension EnvC5.Fullbleed/C5 (Borderless): "459 649" *PaperDimension Env10/Com 10: "297 684" *PaperDimension Env10.Fullbleed/Com 10 (Borderless): "297 684" *PaperDimension EnvDL/DL: "311 623" *PaperDimension EnvDL.Fullbleed/DL (Borderless): "311 623" *PaperDimension 5x13/Eight Point 5x13: "360 936" *PaperDimension 5x13.Fullbleed/Eight Point 5x13 (Borderless): "360 936" *PaperDimension EnvC6/Envelope C6: "323 459" *PaperDimension EnvC6.Fullbleed/Envelope C6 (Borderless): "323 459" *PaperDimension Executive/Executive: "522 756" *PaperDimension Executive.Fullbleed/Executive (Borderless): "522 756" *PaperDimension B5/JIS B5: "518 727" *PaperDimension B5.Fullbleed/JIS B5 (Borderless): "518 727" *PaperDimension B6/JIS B6: "362 518" *PaperDimension B6.Fullbleed/JIS B6 (Borderless): "362 518" *PaperDimension Legal/Legal: "612 1008" *PaperDimension Legal.Fullbleed/Legal (Borderless): "612 1008" *PaperDimension EnvMonarch/Monarch: "279 540" *PaperDimension EnvMonarch.Fullbleed/Monarch (Borderless): "279 540" *PaperDimension 69x95mm/16K: "195 270" *PaperDimension 69x95mm.Fullbleed/16K (Borderless): "195 270" *PaperDimension Statement/Statement: "396 612" *PaperDimension Statement.Fullbleed/Statement (Borderless): "396 612" *JCLOpenUI *manualfeed/Manual Feed of Paper: PickOne *OrderDependency: 100 JCLSetup *manualfeed *Defaultmanualfeed: off *manualfeed off/Off: "@PJL SET MANUALFEED=OFF<0A>" *manualfeed on/On: "@PJL SET MANUALFEED=ON<0A>" *JCLCloseUI: *manualfeed *JCLOpenUI *manualduplex/Manual duplex: PickOne *OrderDependency: 100 JCLSetup *manualduplex *Defaultmanualduplex: off *manualduplex off/Off: "@PJL SET MANUALDUPLEX=OFF<0A>" *manualduplex on/On: "@PJL SET MANUALDUPLEX=ON<0A>" *JCLCloseUI: *manualduplex *JCLOpenUI *Duplex/Double-Sided Printing: PickOne *OrderDependency: 100 JCLSetup *Duplex *DefaultDuplex: None *Duplex None/Off: "@PJL SET DUPLEX=OFF<0A>" *Duplex DuplexNoTumble/Long-Edge binding: "@PJL SET DUPLEX=ON<0A>@PJL SET BINDING=LONGEDGE<0A>" *Duplex DuplexTumble/Short-Edge binding: "@PJL SET DUPLEX=ON<0A>@PJL SET BINDING=SHORTEDGE<0A>" *JCLCloseUI: *Duplex *JCLOpenUI *ColorModel/Color: PickOne *OrderDependency: 100 JCLSetup *ColorModel *DefaultColorModel: color *ColorModel color/Color: "@PJL SET RENDERMODE=COLOR<0A>" *ColorModel grayscale/Black and White: "@PJL SET RENDERMODE=GRAYSCALE<0A>" *JCLCloseUI: *ColorModel *JCLOpenUI *Resolution/Resolution: PickOne *OrderDependency: 100 JCLSetup *Resolution *DefaultResolution: 600dpi *Resolution 300dpi/300 dpi: "@PJL SET RESOLUTION=300<0A>" *Resolution 600dpi/600 dpi: "@PJL SET RESOLUTION=600<0A>" *Resolution 1200dpi/1200 dpi: "@PJL SET RESOLUTION=1200<0A>" *JCLCloseUI: *Resolution *JCLOpenUI *ret/Resolution Enhancement: PickOne *OrderDependency: 100 JCLSetup *ret *Defaultret: notset *ret dark/Dark: "@PJL SET RET=DARK<0A>" *ret light/Light: "@PJL SET RET=LIGHT<0A>" *ret medium/Medium: "@PJL SET RET=MEDIUM<0A>" *ret notset/Not Set: "@PJL SET RET=NOTSET<0A>" *ret off/Off: "@PJL SET RET=OFF<0A>" *JCLCloseUI: *ret *JCLOpenUI *borderless/Borderless Printing: PickOne *OrderDependency: 100 JCLSetup *borderless *Defaultborderless: on *borderless off/Off: "@PJL SET BORDERLESS=OFF<0A>" *borderless on/On: "@PJL SET BORDERLESS=ON<0A>" *JCLCloseUI: *borderless *JCLOpenUI *edgetoedge/Edge to edge: PickOne *OrderDependency: 100 JCLSetup *edgetoedge *Defaultedgetoedge: yes *edgetoedge no/No: "@PJL SET EDGETOEDGE=NO<0A>" *edgetoedge yes/Yes: "@PJL SET EDGETOEDGE=YES<0A>" *JCLCloseUI: *edgetoedge *JCLOpenUI *joboffset/Job Offset: PickOne *OrderDependency: 100 JCLSetup *joboffset *Defaultjoboffset: on *joboffset off/Off: "@PJL SET JOBOFFSET=OFF<0A>" *joboffset on/On: "@PJL SET JOBOFFSET=ON<0A>" *JCLCloseUI: *joboffset *JCLOpenUI *lowsupplies/On low supplies: PickOne *OrderDependency: 100 JCLSetup *lowsupplies *Defaultlowsupplies: continue *lowsupplies continue/Continue: "@PJL SET LOWSUPPLIES=CONTINUE<0A>" *lowsupplies stop/Stop: "@PJL SET LOWSUPPLIES=STOP<0A>" *JCLCloseUI: *lowsupplies *JCLOpenUI *overridea4withletter/Override A4 with Letter: PickOne *OrderDependency: 100 JCLSetup *overridea4withletter *Defaultoverridea4withletter: yes *overridea4withletter no/No: "@PJL SET OVERRIDEA4WITHLETTER=NO<0A>" *overridea4withletter yes/Yes: "@PJL SET OVERRIDEA4WITHLETTER=YES<0A>" *JCLCloseUI: *overridea4withletter *CloseGroup: General *% Generic boilerplate PPD stuff as standard PostScript fonts and so on *DefaultFont: Courier *Font AvantGarde-Book: Standard "(001.006S)" Standard ROM *Font AvantGarde-BookOblique: Standard "(001.006S)" Standard ROM *Font AvantGarde-Demi: Standard "(001.007S)" Standard ROM *Font AvantGarde-DemiOblique: Standard "(001.007S)" Standard ROM *Font Bookman-Demi: Standard "(001.004S)" Standard ROM *Font Bookman-DemiItalic: Standard "(001.004S)" Standard ROM *Font Bookman-Light: Standard "(001.004S)" Standard ROM *Font Bookman-LightItalic: Standard "(001.004S)" Standard ROM *Font Courier: Standard "(002.004S)" Standard ROM *Font Courier-Bold: Standard "(002.004S)" Standard ROM *Font Courier-BoldOblique: Standard "(002.004S)" Standard ROM *Font Courier-Oblique: Standard "(002.004S)" Standard ROM *Font Helvetica: Standard "(001.006S)" Standard ROM *Font Helvetica-Bold: Standard "(001.007S)" Standard ROM *Font Helvetica-BoldOblique: Standard "(001.007S)" Standard ROM *Font Helvetica-Narrow: Standard "(001.006S)" Standard ROM *Font Helvetica-Narrow-Bold: Standard "(001.007S)" Standard ROM *Font Helvetica-Narrow-BoldOblique: Standard "(001.007S)" Standard ROM *Font Helvetica-Narrow-Oblique: Standard "(001.006S)" Standard ROM *Font Helvetica-Oblique: Standard "(001.006S)" Standard ROM *Font NewCenturySchlbk-Bold: Standard "(001.009S)" Standard ROM *Font NewCenturySchlbk-BoldItalic: Standard "(001.007S)" Standard ROM *Font NewCenturySchlbk-Italic: Standard "(001.006S)" Standard ROM *Font NewCenturySchlbk-Roman: Standard "(001.007S)" Standard ROM *Font Palatino-Bold: Standard "(001.005S)" Standard ROM *Font Palatino-BoldItalic: Standard "(001.005S)" Standard ROM *Font Palatino-Italic: Standard "(001.005S)" Standard ROM *Font Palatino-Roman: Standard "(001.005S)" Standard ROM *Font Symbol: Special "(001.007S)" Special ROM *Font Times-Bold: Standard "(001.007S)" Standard ROM *Font Times-BoldItalic: Standard "(001.009S)" Standard ROM *Font Times-Italic: Standard "(001.007S)" Standard ROM *Font Times-Roman: Standard "(001.007S)" Standard ROM *Font ZapfChancery-MediumItalic: Standard "(001.007S)" Standard ROM *Font ZapfDingbats: Special "(001.004S)" Standard ROM