| /* |
| * Simulated client test program for CUPS. |
| * |
| * Copyright © 2017-2019 by Apple Inc. |
| * |
| * Licensed under Apache License v2.0. See the file "LICENSE" for more |
| * information. |
| */ |
| |
| /* |
| * Include necessary headers... |
| */ |
| |
| #include <config.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <cups/cups.h> |
| #include <cups/raster.h> |
| #include <cups/string-private.h> |
| #include <cups/thread-private.h> |
| |
| |
| /* |
| * Constants... |
| */ |
| |
| #define MAX_CLIENTS 16 /* Maximum number of client threads */ |
| |
| |
| /* |
| * Local types... |
| */ |
| |
| typedef struct _client_data_s |
| { |
| const char *uri, /* Printer URI */ |
| *hostname, /* Hostname */ |
| *user, /* Username */ |
| *resource; /* Resource path */ |
| int port; /* Port number */ |
| http_encryption_t encryption; /* Use encryption? */ |
| const char *docfile, /* Document file */ |
| *docformat; /* Document format */ |
| int grayscale, /* Force grayscale? */ |
| keepfile; /* Keep temporary file? */ |
| ipp_pstate_t printer_state; /* Current printer state */ |
| char printer_state_reasons[1024]; |
| /* Current printer-state-reasons */ |
| int job_id; /* Job ID for submitted job */ |
| ipp_jstate_t job_state; /* Current job state */ |
| char job_state_reasons[1024]; |
| /* Current job-state-reasons */ |
| } _client_data_t; |
| |
| |
| /* |
| * Local globals... |
| */ |
| |
| static int client_count = 0; |
| static _cups_mutex_t client_mutex = _CUPS_MUTEX_INITIALIZER; |
| static int verbosity = 0; |
| |
| |
| /* |
| * Local functions... |
| */ |
| |
| static const char *make_raster_file(ipp_t *response, int grayscale, char *tempname, size_t tempsize, const char **format); |
| static void *monitor_printer(_client_data_t *data); |
| static void *run_client(_client_data_t *data); |
| static void show_attributes(const char *title, int request, ipp_t *ipp); |
| static void show_capabilities(ipp_t *response); |
| static void usage(void); |
| |
| |
| /* |
| * 'main()' - Main entry. |
| */ |
| |
| int /* O - Exit status */ |
| main(int argc, /* I - Number of command-line arguments */ |
| char *argv[]) /* I - Command-line arguments */ |
| { |
| int i; /* Looping var */ |
| const char *opt; /* Current option */ |
| int num_clients = 0,/* Number of clients to simulate */ |
| clients_started = 0; |
| /* Number of clients that have been started */ |
| char scheme[32], /* URI scheme */ |
| userpass[256], /* Username:password */ |
| hostname[256], /* Hostname */ |
| resource[256]; /* Resource path */ |
| _client_data_t data; /* Client data */ |
| |
| |
| /* |
| * Parse command-line options... |
| */ |
| |
| if (argc == 1) |
| return (0); |
| |
| memset(&data, 0, sizeof(data)); |
| |
| for (i = 1; i < argc; i ++) |
| { |
| if (argv[i][0] == '-') |
| { |
| for (opt = argv[i] + 1; *opt; opt ++) |
| { |
| switch (*opt) |
| { |
| case 'c' : /* -c num-clients */ |
| if (num_clients) |
| { |
| puts("Number of clients can only be specified once."); |
| usage(); |
| return (1); |
| } |
| |
| i ++; |
| if (i >= argc) |
| { |
| puts("Expected client count after '-c'."); |
| usage(); |
| return (1); |
| } |
| |
| if ((num_clients = atoi(argv[i])) < 1) |
| { |
| puts("Number of clients must be one or more."); |
| usage(); |
| return (1); |
| } |
| break; |
| |
| case 'd' : /* -d document-format */ |
| if (data.docformat) |
| { |
| puts("Document format can only be specified once."); |
| usage(); |
| return (1); |
| } |
| |
| i ++; |
| if (i >= argc) |
| { |
| puts("Expected document format after '-d'."); |
| usage(); |
| return (1); |
| } |
| |
| data.docformat = argv[i]; |
| break; |
| |
| case 'f' : /* -f print-file */ |
| if (data.docfile) |
| { |
| puts("Print file can only be specified once."); |
| usage(); |
| return (1); |
| } |
| |
| i ++; |
| if (i >= argc) |
| { |
| puts("Expected print file after '-f'."); |
| usage(); |
| return (1); |
| } |
| |
| data.docfile = argv[i]; |
| break; |
| |
| case 'g' : |
| data.grayscale = 1; |
| break; |
| |
| case 'k' : |
| data.keepfile = 1; |
| break; |
| |
| case 'v' : |
| verbosity ++; |
| break; |
| |
| default : |
| printf("Unknown option '-%c'.\n", *opt); |
| usage(); |
| return (1); |
| } |
| } |
| } |
| else if (data.uri || (strncmp(argv[i], "ipp://", 6) && strncmp(argv[i], "ipps://", 7))) |
| { |
| printf("Unknown command-line argument '%s'.\n", argv[i]); |
| usage(); |
| return (1); |
| } |
| else |
| data.uri = argv[i]; |
| } |
| |
| /* |
| * Make sure we have everything we need. |
| */ |
| |
| if (!data.uri) |
| { |
| puts("Expected printer URI."); |
| usage(); |
| return (1); |
| } |
| |
| if (num_clients < 1) |
| num_clients = 1; |
| |
| /* |
| * Connect to the printer... |
| */ |
| |
| if (httpSeparateURI(HTTP_URI_CODING_ALL, data.uri, scheme, sizeof(scheme), userpass, sizeof(userpass), hostname, sizeof(hostname), &data.port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK) |
| { |
| printf("Bad printer URI '%s'.\n", data.uri); |
| return (1); |
| } |
| |
| if (!data.port) |
| data.port = IPP_PORT; |
| |
| if (!strcmp(scheme, "https") || !strcmp(scheme, "ipps")) |
| data.encryption = HTTP_ENCRYPTION_ALWAYS; |
| else |
| data.encryption = HTTP_ENCRYPTION_IF_REQUESTED; |
| |
| /* |
| * Start the client threads... |
| */ |
| |
| data.hostname = hostname; |
| data.resource = resource; |
| |
| while (clients_started < num_clients) |
| { |
| _cupsMutexLock(&client_mutex); |
| if (client_count < MAX_CLIENTS) |
| { |
| _cups_thread_t tid; /* New thread */ |
| |
| client_count ++; |
| _cupsMutexUnlock(&client_mutex); |
| tid = _cupsThreadCreate((_cups_thread_func_t)run_client, &data); |
| _cupsThreadDetach(tid); |
| } |
| else |
| { |
| _cupsMutexUnlock(&client_mutex); |
| sleep(1); |
| } |
| } |
| |
| while (client_count > 0) |
| { |
| _cupsMutexLock(&client_mutex); |
| printf("%d RUNNING CLIENTS\n", client_count); |
| _cupsMutexUnlock(&client_mutex); |
| sleep(1); |
| } |
| |
| return (0); |
| } |
| |
| |
| /* |
| * 'make_raster_file()' - Create a temporary raster file. |
| */ |
| |
| static const char * /* O - Print filename */ |
| make_raster_file(ipp_t *response, /* I - Printer attributes */ |
| int grayscale, /* I - Force grayscale? */ |
| char *tempname, /* I - Temporary filename buffer */ |
| size_t tempsize, /* I - Size of temp file buffer */ |
| const char **format) /* O - Print format */ |
| { |
| int i, /* Looping var */ |
| count; /* Number of values */ |
| ipp_attribute_t *attr; /* Printer attribute */ |
| const char *type = NULL; /* Raster type (colorspace + bits) */ |
| pwg_media_t *media = NULL; /* Media size */ |
| int xdpi = 0, /* Horizontal resolution */ |
| ydpi = 0; /* Vertical resolution */ |
| int fd; /* Temporary file */ |
| cups_mode_t mode; /* Raster mode */ |
| cups_raster_t *ras; /* Raster stream */ |
| cups_page_header2_t header; /* Page header */ |
| unsigned char *line, /* Line of raster data */ |
| *lineptr; /* Pointer into line */ |
| unsigned y, /* Current position on page */ |
| xcount, ycount, /* Current count for X and Y */ |
| xrep, yrep, /* Repeat count for X and Y */ |
| xoff, yoff, /* Offsets for X and Y */ |
| yend; /* End Y value */ |
| int temprow, /* Row in template */ |
| tempcolor; /* Template color */ |
| const char *template; /* Pointer into template */ |
| const unsigned char *color; /* Current color */ |
| static const unsigned char colors[][3] = |
| { /* Colors for test */ |
| { 191, 191, 191 }, |
| { 127, 127, 127 }, |
| { 63, 63, 63 }, |
| { 0, 0, 0 }, |
| { 255, 0, 0 }, |
| { 255, 127, 0 }, |
| { 255, 255, 0 }, |
| { 127, 255, 0 }, |
| { 0, 255, 0 }, |
| { 0, 255, 127 }, |
| { 0, 255, 255 }, |
| { 0, 127, 255 }, |
| { 0, 0, 255 }, |
| { 127, 0, 255 }, |
| { 255, 0, 255 } |
| }; |
| static const char * const templates[] = |
| { /* Raster template */ |
| " CCC U U PPPP SSS TTTTT EEEEE SSS TTTTT 000 1 222 333 4 55555 66 77777 888 999 ", |
| "C C U U P P S S T E S S T 0 0 11 2 2 3 3 4 4 5 6 7 8 8 9 9 ", |
| "C U U P P S T E S T 0 0 1 2 3 4 4 5 6 7 8 8 9 9 ", |
| "C U U PPPP SSS ----- T EEEE SSS T 0 0 0 1 22 333 44444 555 6666 7 888 9999 ", |
| "C U U P S T E S T 0 0 1 2 3 4 5 6 6 7 8 8 9 ", |
| "C C U U P S S T E S S T 0 0 1 2 3 3 4 5 5 6 6 7 8 8 9 ", |
| " CCC UUU P SSS T EEEEE SSS T 000 111 22222 333 4 555 666 7 888 99 ", |
| " " |
| }; |
| |
| |
| /* |
| * Figure out the output format... |
| */ |
| |
| if ((attr = ippFindAttribute(response, "document-format-supported", IPP_TAG_MIMETYPE)) == NULL) |
| { |
| puts("No supported document formats, aborting."); |
| return (NULL); |
| } |
| |
| if (*format) |
| { |
| if (!ippContainsString(attr, *format)) |
| { |
| printf("Printer does not support document-format '%s'.\n", *format); |
| return (NULL); |
| } |
| |
| if (!strcmp(*format, "image/urf")) |
| mode = CUPS_RASTER_WRITE_APPLE; |
| else if (!strcmp(*format, "image/pwg-raster")) |
| mode = CUPS_RASTER_WRITE_PWG; |
| else |
| { |
| printf("Unable to generate document-format '%s'.\n", *format); |
| return (NULL); |
| } |
| } |
| else if (ippContainsString(attr, "image/urf")) |
| { |
| /* |
| * Apple Raster format... |
| */ |
| |
| *format = "image/urf"; |
| mode = CUPS_RASTER_WRITE_APPLE; |
| } |
| else if (ippContainsString(attr, "image/pwg-raster")) |
| { |
| /* |
| * PWG Raster format... |
| */ |
| |
| *format = "image/pwg-raster"; |
| mode = CUPS_RASTER_WRITE_PWG; |
| } |
| else |
| { |
| /* |
| * No supported raster format... |
| */ |
| |
| puts("Printer does not support Apple or PWG raster files, aborting."); |
| return (NULL); |
| } |
| |
| /* |
| * Figure out the the media, resolution, and color mode... |
| */ |
| |
| if ((attr = ippFindAttribute(response, "media-default", IPP_TAG_KEYWORD)) != NULL) |
| { |
| /* |
| * Use default media... |
| */ |
| |
| media = pwgMediaForPWG(ippGetString(attr, 0, NULL)); |
| } |
| else if ((attr = ippFindAttribute(response, "media-ready", IPP_TAG_KEYWORD)) != NULL) |
| { |
| /* |
| * Use ready media... |
| */ |
| |
| if (ippContainsString(attr, "na_letter_8.5x11in")) |
| media = pwgMediaForPWG("na_letter_8.5x11in"); |
| else if (ippContainsString(attr, "iso_a4_210x297mm")) |
| media = pwgMediaForPWG("iso_a4_210x297mm"); |
| else |
| media = pwgMediaForPWG(ippGetString(attr, 0, NULL)); |
| } |
| else |
| { |
| puts("No default or ready media reported by printer, aborting."); |
| return (NULL); |
| } |
| |
| if (mode == CUPS_RASTER_WRITE_APPLE && (attr = ippFindAttribute(response, "urf-supported", IPP_TAG_KEYWORD)) != NULL) |
| { |
| for (i = 0, count = ippGetCount(attr); i < count; i ++) |
| { |
| const char *val = ippGetString(attr, i, NULL); |
| |
| if (!strncmp(val, "RS", 2)) |
| xdpi = ydpi = atoi(val + 2); |
| else if (!strncmp(val, "W8", 2) && !type) |
| type = "sgray_8"; |
| else if (!strncmp(val, "SRGB24", 6) && !grayscale) |
| type = "srgb_8"; |
| } |
| } |
| else if (mode == CUPS_RASTER_WRITE_PWG && (attr = ippFindAttribute(response, "pwg-raster-document-resolution-supported", IPP_TAG_RESOLUTION)) != NULL) |
| { |
| for (i = 0, count = ippGetCount(attr); i < count; i ++) |
| { |
| int tempxdpi, tempydpi; |
| ipp_res_t tempunits; |
| |
| tempxdpi = ippGetResolution(attr, 0, &tempydpi, &tempunits); |
| |
| if (i == 0 || tempxdpi < xdpi || tempydpi < ydpi) |
| { |
| xdpi = tempxdpi; |
| ydpi = tempydpi; |
| } |
| } |
| |
| if ((attr = ippFindAttribute(response, "pwg-raster-document-type-supported", IPP_TAG_KEYWORD)) != NULL) |
| { |
| if (!grayscale && ippContainsString(attr, "srgb_8")) |
| type = "srgb_8"; |
| else if (ippContainsString(attr, "sgray_8")) |
| type = "sgray_8"; |
| } |
| } |
| |
| if (xdpi < 72 || ydpi < 72) |
| { |
| puts("No supported raster resolutions, aborting."); |
| return (NULL); |
| } |
| |
| if (!type) |
| { |
| puts("No supported color spaces or bit depths, aborting."); |
| return (NULL); |
| } |
| |
| /* |
| * Make the raster context and details... |
| */ |
| |
| if (!cupsRasterInitPWGHeader(&header, media, type, xdpi, ydpi, "one-sided", NULL)) |
| { |
| printf("Unable to initialize raster context: %s\n", cupsRasterErrorString()); |
| return (NULL); |
| } |
| |
| header.cupsInteger[CUPS_RASTER_PWG_TotalPageCount] = 1; |
| |
| if (header.cupsWidth > (4 * header.HWResolution[0])) |
| { |
| xoff = header.HWResolution[0] / 2; |
| yoff = header.HWResolution[1] / 2; |
| } |
| else |
| { |
| xoff = 0; |
| yoff = 0; |
| } |
| |
| xrep = (header.cupsWidth - 2 * xoff) / 140; |
| yrep = xrep * header.HWResolution[1] / header.HWResolution[0]; |
| yend = header.cupsHeight - yoff; |
| |
| /* |
| * Prepare the raster file... |
| */ |
| |
| if ((line = malloc(header.cupsBytesPerLine)) == NULL) |
| { |
| printf("Unable to allocate %u bytes for raster output: %s\n", header.cupsBytesPerLine, strerror(errno)); |
| return (NULL); |
| } |
| |
| if ((fd = cupsTempFd(tempname, (int)tempsize)) < 0) |
| { |
| printf("Unable to create temporary print file: %s\n", strerror(errno)); |
| free(line); |
| return (NULL); |
| } |
| |
| if ((ras = cupsRasterOpen(fd, mode)) == NULL) |
| { |
| printf("Unable to open raster stream: %s\n", cupsRasterErrorString()); |
| close(fd); |
| free(line); |
| return (NULL); |
| } |
| |
| /* |
| * Write a single page consisting of the template dots repeated over the page. |
| */ |
| |
| cupsRasterWriteHeader2(ras, &header); |
| |
| memset(line, 0xff, header.cupsBytesPerLine); |
| |
| for (y = 0; y < yoff; y ++) |
| cupsRasterWritePixels(ras, line, header.cupsBytesPerLine); |
| |
| for (temprow = 0, tempcolor = 0; y < yend;) |
| { |
| template = templates[temprow]; |
| color = colors[tempcolor]; |
| |
| temprow ++; |
| if (temprow >= (int)(sizeof(templates) / sizeof(templates[0]))) |
| { |
| temprow = 0; |
| tempcolor ++; |
| if (tempcolor >= (int)(sizeof(colors) / sizeof(colors[0]))) |
| tempcolor = 0; |
| else if (tempcolor > 3 && header.cupsColorSpace == CUPS_CSPACE_SW) |
| tempcolor = 0; |
| } |
| |
| memset(line, 0xff, header.cupsBytesPerLine); |
| |
| if (header.cupsColorSpace == CUPS_CSPACE_SW) |
| { |
| /* |
| * Do grayscale output... |
| */ |
| |
| for (lineptr = line + xoff; *template; template ++) |
| { |
| if (*template != ' ') |
| { |
| for (xcount = xrep; xcount > 0; xcount --) |
| *lineptr++ = *color; |
| } |
| else |
| { |
| lineptr += xrep; |
| } |
| } |
| } |
| else |
| { |
| /* |
| * Do color output... |
| */ |
| |
| for (lineptr = line + 3 * xoff; *template; template ++) |
| { |
| if (*template != ' ') |
| { |
| for (xcount = xrep; xcount > 0; xcount --, lineptr += 3) |
| memcpy(lineptr, color, 3); |
| } |
| else |
| { |
| lineptr += 3 * xrep; |
| } |
| } |
| } |
| |
| for (ycount = yrep; ycount > 0 && y < yend; ycount --, y ++) |
| cupsRasterWritePixels(ras, line, header.cupsBytesPerLine); |
| } |
| |
| memset(line, 0xff, header.cupsBytesPerLine); |
| |
| for (y = 0; y < header.cupsHeight; y ++) |
| cupsRasterWritePixels(ras, line, header.cupsBytesPerLine); |
| |
| cupsRasterClose(ras); |
| |
| close(fd); |
| |
| printf("PRINT FILE: %s\n", tempname); |
| |
| return (tempname); |
| } |
| |
| |
| /* |
| * 'monitor_printer()' - Monitor the job and printer states. |
| */ |
| |
| static void * /* O - Thread exit code */ |
| monitor_printer( |
| _client_data_t *data) /* I - Client data */ |
| { |
| http_t *http; /* Connection to printer */ |
| ipp_t *request, /* IPP request */ |
| *response; /* IPP response */ |
| ipp_attribute_t *attr; /* Attribute in response */ |
| ipp_pstate_t printer_state; /* Printer state */ |
| char printer_state_reasons[1024]; |
| /* Printer state reasons */ |
| ipp_jstate_t job_state; /* Job state */ |
| char job_state_reasons[1024];/* Printer state reasons */ |
| static const char * const jattrs[] = /* Job attributes we want */ |
| { |
| "job-state", |
| "job-state-reasons" |
| }; |
| static const char * const pattrs[] = /* Printer attributes we want */ |
| { |
| "printer-state", |
| "printer-state-reasons" |
| }; |
| |
| |
| /* |
| * Open a connection to the printer... |
| */ |
| |
| http = httpConnect2(data->hostname, data->port, NULL, AF_UNSPEC, data->encryption, 1, 0, NULL); |
| |
| /* |
| * Loop until the job is canceled, aborted, or completed. |
| */ |
| |
| printer_state = (ipp_pstate_t)0; |
| printer_state_reasons[0] = '\0'; |
| |
| job_state = (ipp_jstate_t)0; |
| job_state_reasons[0] = '\0'; |
| |
| while (data->job_state < IPP_JSTATE_CANCELED) |
| { |
| /* |
| * Reconnect to the printer as needed... |
| */ |
| |
| if (httpGetFd(http) < 0) |
| httpReconnect2(http, 30000, NULL); |
| |
| if (httpGetFd(http) >= 0) |
| { |
| /* |
| * Connected, so check on the printer state... |
| */ |
| |
| request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES); |
| ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, data->uri); |
| ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); |
| ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", (int)(sizeof(pattrs) / sizeof(pattrs[0])), NULL, pattrs); |
| |
| response = cupsDoRequest(http, request, data->resource); |
| |
| if ((attr = ippFindAttribute(response, "printer-state", IPP_TAG_ENUM)) != NULL) |
| printer_state = (ipp_pstate_t)ippGetInteger(attr, 0); |
| |
| if ((attr = ippFindAttribute(response, "printer-state-reasons", IPP_TAG_KEYWORD)) != NULL) |
| ippAttributeString(attr, printer_state_reasons, sizeof(printer_state_reasons)); |
| |
| if (printer_state != data->printer_state || strcmp(printer_state_reasons, data->printer_state_reasons)) |
| { |
| printf("PRINTER: %s (%s)\n", ippEnumString("printer-state", (int)printer_state), printer_state_reasons); |
| |
| data->printer_state = printer_state; |
| strlcpy(data->printer_state_reasons, printer_state_reasons, sizeof(data->printer_state_reasons)); |
| } |
| |
| ippDelete(response); |
| |
| if (data->job_id > 0) |
| { |
| /* |
| * Check the status of the job itself... |
| */ |
| |
| request = ippNewRequest(IPP_OP_GET_JOB_ATTRIBUTES); |
| ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, data->uri); |
| ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", data->job_id); |
| ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); |
| ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", (int)(sizeof(jattrs) / sizeof(jattrs[0])), NULL, jattrs); |
| |
| response = cupsDoRequest(http, request, data->resource); |
| |
| if ((attr = ippFindAttribute(response, "job-state", IPP_TAG_ENUM)) != NULL) |
| job_state = (ipp_jstate_t)ippGetInteger(attr, 0); |
| |
| if ((attr = ippFindAttribute(response, "job-state-reasons", IPP_TAG_KEYWORD)) != NULL) |
| ippAttributeString(attr, job_state_reasons, sizeof(job_state_reasons)); |
| |
| if (job_state != data->job_state || strcmp(job_state_reasons, data->job_state_reasons)) |
| { |
| printf("JOB %d: %s (%s)\n", data->job_id, ippEnumString("job-state", (int)job_state), job_state_reasons); |
| |
| data->job_state = job_state; |
| strlcpy(data->job_state_reasons, job_state_reasons, sizeof(data->job_state_reasons)); |
| } |
| |
| ippDelete(response); |
| } |
| } |
| |
| if (data->job_state < IPP_JSTATE_CANCELED) |
| { |
| /* |
| * Sleep for 5 seconds... |
| */ |
| |
| sleep(5); |
| } |
| } |
| |
| /* |
| * Cleanup and return... |
| */ |
| |
| httpClose(http); |
| |
| printf("FINISHED MONITORING JOB %d\n", data->job_id); |
| |
| return (NULL); |
| } |
| |
| |
| /* |
| * 'run_client()' - Run a client thread. |
| */ |
| |
| static void * /* O - Thread exit code */ |
| run_client( |
| _client_data_t *data) /* I - Client data */ |
| { |
| _cups_thread_t monitor_id; /* Monitoring thread ID */ |
| const char *name; /* Job name */ |
| char tempfile[1024] = ""; /* Temporary file (if any) */ |
| _client_data_t ldata; /* Local client data */ |
| http_t *http; /* Connection to printer */ |
| ipp_t *request, /* IPP request */ |
| *response; /* IPP response */ |
| ipp_attribute_t *attr; /* Attribute in response */ |
| static const char * const pattrs[] = /* Printer attributes we are interested in */ |
| { |
| "job-template", |
| "printer-defaults", |
| "printer-description", |
| "media-col-database", |
| "media-col-ready" |
| }; |
| |
| |
| ldata = *data; |
| |
| /* |
| * Start monitoring the printer in the background... |
| */ |
| |
| monitor_id = _cupsThreadCreate((_cups_thread_func_t)monitor_printer, &ldata); |
| |
| /* |
| * Open a connection to the printer... |
| */ |
| |
| http = httpConnect2(data->hostname, data->port, NULL, AF_UNSPEC, data->encryption, 1, 0, NULL); |
| |
| /* |
| * Query printer status and capabilities... |
| */ |
| |
| request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES); |
| ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, ldata.uri); |
| ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); |
| ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", (int)(sizeof(pattrs) / sizeof(pattrs[0])), NULL, pattrs); |
| |
| response = cupsDoRequest(http, request, ldata.resource); |
| |
| if (verbosity) |
| show_capabilities(response); |
| |
| /* |
| * Now figure out what we will be printing... |
| */ |
| |
| if (ldata.docfile) |
| { |
| /* |
| * User specified a print file, figure out the format... |
| */ |
| const char *ext; /* Filename extension */ |
| |
| if ((ext = strrchr(ldata.docfile, '.')) != NULL) |
| { |
| /* |
| * Guess the format from the extension... |
| */ |
| |
| if (!strcmp(ext, ".jpg")) |
| ldata.docformat = "image/jpeg"; |
| else if (!strcmp(ext, ".pdf")) |
| ldata.docformat = "application/pdf"; |
| else if (!strcmp(ext, ".ps")) |
| ldata.docformat = "application/postscript"; |
| else if (!strcmp(ext, ".pwg")) |
| ldata.docformat = "image/pwg-raster"; |
| else if (!strcmp(ext, ".urf")) |
| ldata.docformat = "image/urf"; |
| else |
| ldata.docformat = "application/octet-stream"; |
| } |
| else |
| { |
| /* |
| * Tell the printer to auto-detect... |
| */ |
| |
| ldata.docformat = "application/octet-stream"; |
| } |
| } |
| else |
| { |
| /* |
| * No file specified, make something to test with... |
| */ |
| |
| if ((ldata.docfile = make_raster_file(response, ldata.grayscale, tempfile, sizeof(tempfile), &ldata.docformat)) == NULL) |
| return ((void *)1); |
| } |
| |
| ippDelete(response); |
| |
| /* |
| * Create a job and wait for completion... |
| */ |
| |
| request = ippNewRequest(IPP_OP_CREATE_JOB); |
| ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, ldata.uri); |
| ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); |
| |
| if ((name = strrchr(ldata.docfile, '/')) != NULL) |
| name ++; |
| else |
| name = ldata.docfile; |
| |
| ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL, name); |
| |
| if (verbosity) |
| show_attributes("Create-Job request", 1, request); |
| |
| response = cupsDoRequest(http, request, ldata.resource); |
| |
| if (verbosity) |
| show_attributes("Create-Job response", 0, response); |
| |
| if (cupsLastError() >= IPP_STATUS_REDIRECTION_OTHER_SITE) |
| { |
| printf("Unable to create print job: %s\n", cupsLastErrorString()); |
| |
| ldata.job_state = IPP_JSTATE_ABORTED; |
| |
| goto cleanup; |
| } |
| |
| if ((attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) == NULL) |
| { |
| puts("No job-id returned in Create-Job request."); |
| |
| ldata.job_state = IPP_JSTATE_ABORTED; |
| |
| goto cleanup; |
| } |
| |
| ldata.job_id = ippGetInteger(attr, 0); |
| |
| printf("CREATED JOB %d, sending %s of type %s\n", ldata.job_id, ldata.docfile, ldata.docformat); |
| |
| ippDelete(response); |
| |
| request = ippNewRequest(IPP_OP_SEND_DOCUMENT); |
| ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, ldata.uri); |
| ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", ldata.job_id); |
| ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); |
| ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE, "document-format", NULL, ldata.docformat); |
| ippAddBoolean(request, IPP_TAG_OPERATION, "last-document", 1); |
| |
| if (verbosity) |
| show_attributes("Send-Document request", 1, request); |
| |
| response = cupsDoFileRequest(http, request, ldata.resource, ldata.docfile); |
| |
| if (verbosity) |
| show_attributes("Send-Document response", 0, response); |
| |
| if (cupsLastError() >= IPP_STATUS_REDIRECTION_OTHER_SITE) |
| { |
| printf("Unable to print file: %s\n", cupsLastErrorString()); |
| |
| ldata.job_state = IPP_JSTATE_ABORTED; |
| |
| goto cleanup; |
| } |
| |
| puts("WAITING FOR JOB TO COMPLETE"); |
| |
| while (ldata.job_state < IPP_JSTATE_CANCELED) |
| sleep(1); |
| |
| /* |
| * Cleanup after ourselves... |
| */ |
| |
| cleanup: |
| |
| httpClose(http); |
| |
| if (tempfile[0] && !ldata.keepfile) |
| unlink(tempfile); |
| |
| _cupsThreadWait(monitor_id); |
| |
| _cupsMutexLock(&client_mutex); |
| client_count --; |
| _cupsMutexUnlock(&client_mutex); |
| |
| return (NULL); |
| } |
| |
| |
| /* |
| * 'show_attributes()' - Show attributes in a request or response. |
| */ |
| |
| static void |
| show_attributes(const char *title, /* I - Title */ |
| int request, /* I - 1 for request, 0 for response */ |
| ipp_t *ipp) /* I - IPP request/response */ |
| { |
| int minor, major = ippGetVersion(ipp, &minor); |
| /* IPP version number */ |
| ipp_tag_t group = IPP_TAG_ZERO; |
| /* Current group tag */ |
| ipp_attribute_t *attr; /* Current attribute */ |
| const char *name; /* Attribute name */ |
| char buffer[1024]; /* Value */ |
| |
| |
| printf("%s:\n", title); |
| printf(" version=%d.%d\n", major, minor); |
| printf(" request-id=%d\n", ippGetRequestId(ipp)); |
| if (!request) |
| printf(" status-code=%s\n", ippErrorString(ippGetStatusCode(ipp))); |
| |
| for (attr = ippFirstAttribute(ipp); attr; attr = ippNextAttribute(ipp)) |
| { |
| if (group != ippGetGroupTag(attr)) |
| { |
| group = ippGetGroupTag(attr); |
| if (group) |
| printf(" %s:\n", ippTagString(group)); |
| } |
| |
| if ((name = ippGetName(attr)) != NULL) |
| { |
| ippAttributeString(attr, buffer, sizeof(buffer)); |
| printf(" %s(%s%s)=%s\n", name, ippGetCount(attr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attr)), buffer); |
| } |
| } |
| } |
| |
| |
| /* |
| * 'show_capabilities()' - Show printer capabilities. |
| */ |
| |
| static void |
| show_capabilities(ipp_t *response) /* I - Printer attributes */ |
| { |
| int i; /* Looping var */ |
| ipp_attribute_t *attr; /* Attribute */ |
| char buffer[1024]; /* Attribute value buffer */ |
| static const char * const pattrs[] = /* Attributes we want to show */ |
| { |
| "copies-default", |
| "copies-supported", |
| "finishings-default", |
| "finishings-ready", |
| "finishings-supported", |
| "media-default", |
| "media-ready", |
| "media-supported", |
| "output-bin-default", |
| "output-bin-supported", |
| "print-color-mode-default", |
| "print-color-mode-supported", |
| "sides-default", |
| "sides-supported", |
| "document-format-default", |
| "document-format-supported", |
| "pwg-raster-document-resolution-supported", |
| "pwg-raster-document-type-supported", |
| "urf-supported" |
| }; |
| |
| |
| puts("CAPABILITIES:"); |
| for (i = 0; i < (int)(sizeof(pattrs) / sizeof(pattrs[0])); i ++) |
| { |
| if ((attr = ippFindAttribute(response, pattrs[i], IPP_TAG_ZERO)) != NULL) |
| { |
| ippAttributeString(attr, buffer, sizeof(buffer)); |
| printf(" %s=%s\n", pattrs[i], buffer); |
| } |
| } |
| } |
| |
| |
| /* |
| * 'usage()' - Show program usage... |
| */ |
| |
| static void |
| usage(void) |
| { |
| puts("Usage: ./testclient printer-uri [options]"); |
| puts("Options:"); |
| puts(" -c num-clients Simulate multiple clients"); |
| puts(" -d document-format Generate the specified format"); |
| puts(" -f print-file Print the named file"); |
| puts(" -g Force grayscale printing"); |
| puts(" -k Keep temporary files"); |
| puts(" -v Be more verbose"); |
| } |