#include <stdio.h>
#include <stdlib.h>
#include <cups/cups.h>
#include <cups/raster.h>
#include <cups/string-private.h>
#include <cups/thread-private.h>
typedef struct _client_monitor_s
{
const char *uri,
*hostname,
*user,
*resource;
int port;
http_encryption_t encryption;
ipp_pstate_t printer_state;
char printer_state_reasons[1024];
int job_id;
ipp_jstate_t job_state;
char job_state_reasons[1024];
} _client_monitor_t;
static const char *make_raster_file(ipp_t *response, int grayscale, char *tempname, size_t tempsize, const char **format);
static void *monitor_printer(_client_monitor_t *monitor);
static void show_attributes(const char *title, int request, ipp_t *ipp);
static void show_capabilities(ipp_t *response);
static void usage(void);
int
main(int argc,
char *argv[])
{
int i;
const char *opt,
*uri = NULL,
*printfile = NULL,
*printformat = NULL;
int keepfile = 0,
grayscale = 0,
verbosity = 0;
char tempfile[1024] = "",
scheme[32],
userpass[256],
hostname[256],
resource[256];
int port;
http_encryption_t encryption;
_client_monitor_t monitor;
http_t *http;
ipp_t *request,
*response;
ipp_attribute_t *attr;
static const char * const pattrs[] =
{
"job-template",
"printer-defaults",
"printer-description",
"media-col-database",
"media-col-ready"
};
for (i = 1; i < argc; i ++)
{
if (argv[i][0] == '-')
{
for (opt = argv[i] + 1; *opt; opt ++)
{
switch (*opt)
{
case 'd' :
if (printformat)
{
puts("Document format can only be specified once.");
usage();
return (1);
}
i ++;
if (i >= argc)
{
puts("Expected document format after '-d'.");
usage();
return (1);
}
printformat = argv[i];
break;
case 'f' :
if (printfile)
{
puts("Print file can only be specified once.");
usage();
return (1);
}
i ++;
if (i >= argc)
{
puts("Expected print file after '-f'.");
usage();
return (1);
}
printfile = argv[i];
break;
case 'g' :
grayscale = 1;
break;
case 'k' :
keepfile = 1;
break;
case 'v' :
verbosity ++;
break;
default :
printf("Unknown option '-%c'.\n", *opt);
usage();
return (1);
}
}
}
else if (uri || (strncmp(argv[i], "ipp://", 6) && strncmp(argv[i], "ipps://", 7)))
{
printf("Unknown command-line argument '%s'.\n", argv[i]);
usage();
return (1);
}
else
uri = argv[i];
}
if (!uri)
{
puts("Expected printer URI.");
usage();
return (1);
}
if (httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme), userpass, sizeof(userpass), hostname, sizeof(hostname), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
{
printf("Bad printer URI '%s'.\n", uri);
return (1);
}
if (!port)
port = IPP_PORT;
if (!strcmp(scheme, "https") || !strcmp(scheme, "ipps"))
encryption = HTTP_ENCRYPTION_ALWAYS;
else
encryption = HTTP_ENCRYPTION_IF_REQUESTED;
if ((http = httpConnect2(hostname, port, NULL, AF_UNSPEC, encryption, 1, 0, NULL)) == NULL)
{
printf("Unable to connect to '%s' on port %d: %s\n", hostname, port, cupsLastErrorString());
return (1);
}
request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, 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, resource);
if (verbosity)
show_capabilities(response);
if (printfile)
{
if ((opt = strrchr(printfile, '.')) != NULL)
{
if (!strcmp(opt, ".jpg"))
printformat = "image/jpeg";
else if (!strcmp(opt, ".pdf"))
printformat = "application/pdf";
else if (!strcmp(opt, ".ps"))
printformat = "application/postscript";
else if (!strcmp(opt, ".pwg"))
printformat = "image/pwg-raster";
else if (!strcmp(opt, ".urf"))
printformat = "image/urf";
else
printformat = "application/octet-stream";
}
else
{
printformat = "application/octet-stream";
}
}
else
{
if ((printfile = make_raster_file(response, grayscale, tempfile, sizeof(tempfile), &printformat)) == NULL)
return (1);
}
ippDelete(response);
memset(&monitor, 0, sizeof(monitor));
monitor.uri = uri;
monitor.hostname = hostname;
monitor.resource = resource;
monitor.port = port;
monitor.encryption = encryption;
_cupsThreadCreate((_cups_thread_func_t)monitor_printer, &monitor);
request = ippNewRequest(IPP_OP_CREATE_JOB);
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());
if ((opt = strrchr(printfile, '/')) != NULL)
opt ++;
else
opt = printfile;
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL, opt);
if (verbosity)
show_attributes("Create-Job request", 1, request);
response = cupsDoRequest(http, request, 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());
monitor.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.");
monitor.job_state = IPP_JSTATE_ABORTED;
goto cleanup;
}
monitor.job_id = ippGetInteger(attr, 0);
printf("CREATED JOB %d, sending %s of type %s\n", monitor.job_id, printfile, printformat);
ippDelete(response);
request = ippNewRequest(IPP_OP_SEND_DOCUMENT);
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", monitor.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, printformat);
ippAddBoolean(request, IPP_TAG_OPERATION, "last-document", 1);
if (verbosity)
show_attributes("Send-Document request", 1, request);
response = cupsDoFileRequest(http, request, resource, printfile);
if (verbosity)
show_attributes("Send-Document response", 0, response);
if (cupsLastError() >= IPP_STATUS_REDIRECTION_OTHER_SITE)
{
printf("Unable to print file: %s\n", cupsLastErrorString());
monitor.job_state = IPP_JSTATE_ABORTED;
goto cleanup;
}
puts("WAITING FOR JOB TO COMPLETE");
while (monitor.job_state < IPP_JSTATE_CANCELED)
sleep(1);
cleanup:
httpClose(http);
if (tempfile[0] && !keepfile)
unlink(tempfile);
return (monitor.job_state == IPP_JSTATE_COMPLETED);
}
static const char *
make_raster_file(ipp_t *response,
int grayscale,
char *tempname,
size_t tempsize,
const char **format)
{
int i,
count;
ipp_attribute_t *attr;
const char *type = NULL;
pwg_media_t *media = NULL;
int xdpi = 0,
ydpi = 0;
int fd;
cups_mode_t mode;
cups_raster_t *ras;
cups_page_header2_t header;
unsigned char *line,
*lineptr;
unsigned y,
xcount, ycount,
xrep, yrep,
xoff, yoff,
yend;
int temprow,
tempcolor;
const char *template;
const unsigned char *color;
static const unsigned char colors[][3] =
{
{ 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[] =
{
" 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 ",
" "
};
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"))
{
*format = "image/urf";
mode = CUPS_RASTER_WRITE_APPLE;
}
else if (ippContainsString(attr, "image/pwg-raster"))
{
*format = "image/pwg-raster";
mode = CUPS_RASTER_WRITE_PWG;
}
else
{
puts("Printer does not support Apple or PWG raster files, aborting.");
return (NULL);
}
if ((attr = ippFindAttribute(response, "media-default", IPP_TAG_KEYWORD)) != NULL)
{
media = pwgMediaForPWG(ippGetString(attr, 0, NULL));
}
else if ((attr = ippFindAttribute(response, "media-ready", IPP_TAG_KEYWORD)) != NULL)
{
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);
}
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;
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));
return (NULL);
}
if ((ras = cupsRasterOpen(fd, mode)) == NULL)
{
printf("Unable to open raster stream: %s\n", cupsRasterErrorString());
close(fd);
return (NULL);
}
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)
{
for (lineptr = line + xoff; *template; template ++)
{
if (*template != ' ')
{
for (xcount = xrep; xcount > 0; xcount --)
*lineptr++ = *color;
}
else
{
lineptr += xrep;
}
}
}
else
{
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);
}
static void *
monitor_printer(
_client_monitor_t *monitor)
{
http_t *http;
ipp_t *request,
*response;
ipp_attribute_t *attr;
ipp_pstate_t printer_state;
char printer_state_reasons[1024];
ipp_jstate_t job_state;
char job_state_reasons[1024];
static const char * const jattrs[] =
{
"job-state",
"job-state-reasons"
};
static const char * const pattrs[] =
{
"printer-state",
"printer-state-reasons"
};
http = httpConnect2(monitor->hostname, monitor->port, NULL, AF_UNSPEC, monitor->encryption, 1, 0, NULL);
printer_state = (ipp_pstate_t)0;
printer_state_reasons[0] = '\0';
job_state = (ipp_jstate_t)0;
job_state_reasons[0] = '\0';
while (monitor->job_state < IPP_JSTATE_CANCELED)
{
if (httpGetFd(http) < 0)
httpReconnect2(http, 30000, NULL);
if (httpGetFd(http) >= 0)
{
request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, monitor->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, monitor->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 != monitor->printer_state || strcmp(printer_state_reasons, monitor->printer_state_reasons))
{
printf("PRINTER: %s (%s)\n", ippEnumString("printer-state", printer_state), printer_state_reasons);
monitor->printer_state = printer_state;
strlcpy(monitor->printer_state_reasons, printer_state_reasons, sizeof(monitor->printer_state_reasons));
}
ippDelete(response);
if (monitor->job_id > 0)
{
request = ippNewRequest(IPP_OP_GET_JOB_ATTRIBUTES);
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, monitor->uri);
ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", monitor->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, monitor->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 != monitor->job_state || strcmp(job_state_reasons, monitor->job_state_reasons))
{
printf("JOB %d: %s (%s)\n", monitor->job_id, ippEnumString("job-state", job_state), job_state_reasons);
monitor->job_state = job_state;
strlcpy(monitor->job_state_reasons, job_state_reasons, sizeof(monitor->job_state_reasons));
}
ippDelete(response);
}
}
if (monitor->job_state < IPP_JSTATE_CANCELED)
{
sleep(5);
}
}
httpClose(http);
return (NULL);
}
static void
show_attributes(const char *title,
int request,
ipp_t *ipp)
{
int minor, major = ippGetVersion(ipp, &minor);
ipp_tag_t group = IPP_TAG_ZERO;
ipp_attribute_t *attr;
const char *name;
char buffer[1024];
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);
}
}
}
static void
show_capabilities(ipp_t *response)
{
int i;
ipp_attribute_t *attr;
char buffer[1024];
static const char * const pattrs[] =
{
"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);
}
}
}
static void
usage(void)
{
puts("Usage: ./testclient printer-uri [options]");
puts("Options:");
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");
}