]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - test/ippeveprinter.c
Point stdout to the output device or spool directory.
[thirdparty/cups.git] / test / ippeveprinter.c
index fcd6fe0dc877e3181e4cfc7e32f506e30fbd3c60..22c35c50ca7d4f24e806cc7ae687c6f3ee8af3f9 100644 (file)
  */
 
 #include <cups/cups-private.h>
-#include <cups/ppd-private.h>
+#if !CUPS_LITE
+#  include <cups/ppd-private.h>
+#endif /* !CUPS_LITE */
+
 #include <limits.h>
 #include <sys/stat.h>
 
@@ -60,6 +63,8 @@ extern char **environ;
 #  include <sys/vfs.h>
 #endif /* HAVE_SYS_VFS_H */
 
+#include "printer-png.h"
+
 
 /*
  * Constants...
@@ -166,7 +171,9 @@ typedef struct ippeve_printer_s             /**** Printer data ****/
                        *hostname,      /* Hostname */
                        *uri,           /* printer-uri-supported */
                        *device_uri,    /* Device URI (if any) */
+#if !CUPS_LITE
                        *ppdfile,       /* PPD file (if any) */
+#endif /* !CUPS_LITE */
                        *command;       /* Command to run with job file */
   int                  port;           /* Port */
   size_t               urilen;         /* Length of printer URI */
@@ -232,7 +239,7 @@ static int          create_job_file(ippeve_printer_t *printer, ippeve_job_t *job, char *
 static int             create_listener(const char *name, int port, int family);
 static ipp_t           *create_media_col(const char *media, const char *source, const char *type, int width, int length, int bottom, int left, int right, int top);
 static ipp_t           *create_media_size(int width, int length);
-static ippeve_printer_t        *create_printer(const char *servername, int serverport, const char *name, const char *location, const char *icon, cups_array_t *docformats, const char *subtypes, const char *directory, const char *command, const char *ppdfile, const char *device_uri, ipp_t *attrs);
+static ippeve_printer_t        *create_printer(const char *servername, int serverport, const char *name, const char *location, const char *icon, cups_array_t *docformats, const char *subtypes, const char *directory, const char *command, const char *device_uri, ipp_t *attrs);
 static void            debug_attributes(const char *title, ipp_t *ipp, int response);
 static void            delete_client(ippeve_client_t *client);
 static void            delete_job(ippeve_job_t *job);
@@ -250,7 +257,7 @@ static void         finish_document_data(ippeve_client_t *client, ippeve_job_t *job);
 static void            finish_document_uri(ippeve_client_t *client, ippeve_job_t *job);
 static void            html_escape(ippeve_client_t *client, const char *s, size_t slen);
 static void            html_footer(ippeve_client_t *client);
-static void            html_header(ippeve_client_t *client, const char *title);
+static void            html_header(ippeve_client_t *client, const char *title, int refresh);
 static void            html_printf(ippeve_client_t *client, const char *format, ...) _CUPS_FORMAT(2, 3);
 static void            ipp_cancel_job(ippeve_client_t *client);
 static void            ipp_close_job(ippeve_client_t *client);
@@ -266,7 +273,9 @@ static void         ipp_send_uri(ippeve_client_t *client);
 static void            ipp_validate_job(ippeve_client_t *client);
 static ipp_t           *load_ippserver_attributes(const char *servername, int serverport, const char *filename, cups_array_t *docformats);
 static ipp_t           *load_legacy_attributes(const char *make, const char *model, int ppm, int ppm_color, int duplex, cups_array_t *docformats);
+#if !CUPS_LITE
 static ipp_t           *load_ppd_attributes(const char *ppdfile, cups_array_t *docformats);
+#endif /* !CUPS_LITE */
 static int             parse_options(ippeve_client_t *client, cups_option_t **options);
 static void            process_attr_message(ippeve_job_t *job, char *message);
 static void            *process_client(ippeve_client_t *client);
@@ -279,6 +288,9 @@ static int          respond_http(ippeve_client_t *client, http_status_t code, const char
 static void            respond_ipp(ippeve_client_t *client, ipp_status_t status, const char *message, ...) _CUPS_FORMAT(3, 4);
 static void            respond_unsupported(ippeve_client_t *client, ipp_attribute_t *attr);
 static void            run_printer(ippeve_printer_t *printer);
+static int             show_media(ippeve_client_t *client);
+static int             show_status(ippeve_client_t *client);
+static int             show_supplies(ippeve_client_t *client);
 static char            *time_string(time_t tv, char *buffer, size_t bufsize);
 static void            usage(int status) _CUPS_NORETURN;
 static int             valid_doc_attributes(ippeve_client_t *client);
@@ -322,7 +334,9 @@ main(int  argc,                             /* I - Number of command-line args */
                *make = "Test",         /* Manufacturer */
                *model = "Printer",     /* Model */
                *name = NULL,           /* Printer name */
+#if !CUPS_LITE
                *ppdfile = NULL,        /* PPD file */
+#endif /* !CUPS_LITE */
                *subtypes = "_print";   /* DNS-SD service subtype */
   int          legacy = 0,             /* Legacy mode? */
                duplex = 0,             /* Duplex mode */
@@ -394,6 +408,7 @@ main(int  argc,                             /* I - Number of command-line args */
              legacy = 1;
              break;
 
+#if !CUPS_LITE
           case 'P' : /* -P filename.ppd */
              i ++;
              if (i >= argc)
@@ -401,6 +416,7 @@ main(int  argc,                             /* I - Number of command-line args */
 
               ppdfile = argv[i];
               break;
+#endif /* !CUPS_LITE */
 
           case 'V' : /* -V max-version */
              i ++;
@@ -536,8 +552,13 @@ main(int  argc,                            /* I - Number of command-line args */
   if (!name)
     usage(1);
 
+#if CUPS_LITE
+  if (attrfile != NULL && legacy)
+    usage(1);
+#else
   if (((ppdfile != NULL) + (attrfile != NULL) + legacy) > 1)
     usage(1);
+#endif /* CUPS_LITE */
 
  /*
   * Apply defaults as needed...
@@ -613,14 +634,21 @@ main(int  argc,                           /* I - Number of command-line args */
 
   if (attrfile)
     attrs = load_ippserver_attributes(servername, serverport, attrfile, docformats);
+#if !CUPS_LITE
   else if (ppdfile)
     attrs = load_ppd_attributes(ppdfile, docformats);
+#endif /* !CUPS_LITE */
   else
     attrs = load_legacy_attributes(make, model, ppm, ppm_color, duplex, docformats);
 
-  if ((printer = create_printer(servername, serverport, name, location, icon, docformats, subtypes, directory, command, ppdfile, device_uri, attrs)) == NULL)
+  if ((printer = create_printer(servername, serverport, name, location, icon, docformats, subtypes, directory, command, device_uri, attrs)) == NULL)
     return (1);
 
+#if !CUPS_LITE
+  if (ppdfile)
+    printer->ppdfile = strdup(ppdfile);
+#endif /* !CUPS_LITE */
+
  /*
   * Run the print service...
   */
@@ -1140,10 +1168,14 @@ create_media_col(const char *media,     /* I - Media name */
   ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-key", NULL, media_key);
   ippAddCollection(media_col, IPP_TAG_PRINTER, "media-size", media_size);
   ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-size-name", NULL, media);
-  ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin", bottom);
-  ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin", left);
-  ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin", right);
-  ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin", top);
+  if (bottom >= 0)
+    ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin", bottom);
+  if (left >= 0)
+    ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin", left);
+  if (right >= 0)
+    ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin", right);
+  if (top >= 0)
+    ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin", top);
   if (source)
     ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-source", NULL, source);
   if (type)
@@ -1189,7 +1221,6 @@ create_printer(
     const char   *subtypes,            /* I - Bonjour service subtype(s) */
     const char   *directory,           /* I - Spool directory */
     const char   *command,             /* I - Command to run on job files, if any */
-    const char   *ppdfile,             /* I - PPD file, if any */
     const char   *device_uri,          /* I - Output device, if any */
     ipp_t        *attrs)               /* I - Capability attributes */
 {
@@ -1386,7 +1417,6 @@ create_printer(
   printer->directory     = strdup(directory);
   printer->icon          = icon ? strdup(icon) : NULL;
   printer->port          = serverport;
-  printer->ppdfile       = ppdfile ? strdup(ppdfile) : NULL;
   printer->start_time    = time(NULL);
   printer->config_time   = printer->start_time;
   printer->state         = IPP_PSTATE_IDLE;
@@ -1841,6 +1871,10 @@ delete_printer(ippeve_printer_t *printer)        /* I - Printer */
     free(printer->icon);
   if (printer->command)
     free(printer->command);
+  if (printer->device_uri)
+    free(printer->device_uri);
+  if (printer->ppdfile)
+    free(printer->ppdfile);
   if (printer->directory)
     free(printer->directory);
   if (printer->hostname)
@@ -2516,7 +2550,8 @@ html_footer(ippeve_client_t *client)      /* I - Client */
 
 static void
 html_header(ippeve_client_t *client,   /* I - Client */
-            const char    *title)      /* I - Title */
+            const char    *title,      /* I - Title */
+            int           refresh)     /* I - Refresh timer, if any */
 {
   html_printf(client,
              "<!doctype html>\n"
@@ -2525,7 +2560,10 @@ html_header(ippeve_client_t *client,     /* I - Client */
              "<title>%s</title>\n"
              "<link rel=\"shortcut icon\" href=\"/icon.png\" type=\"image/png\">\n"
              "<link rel=\"apple-touch-icon\" href=\"/icon.png\" type=\"image/png\">\n"
-             "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=9\">\n"
+             "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=9\">\n", title);
+  if (refresh > 0)
+    html_printf(client, "<meta http-equiv=\"refresh\" content=\"%d\">\n", refresh);
+  html_printf(client,
              "<meta name=\"viewport\" content=\"width=device-width\">\n"
              "<style>\n"
              "body { font-family: sans-serif; margin: 0; }\n"
@@ -2554,7 +2592,7 @@ html_header(ippeve_client_t *client,      /* I - Client */
              "<td class=\"nav%s\"><a href=\"/supplies\">Supplies</a></td>"
              "<td class=\"nav%s\"><a href=\"/media\">Media</a></td>"
              "</tr></table>\n"
-             "<div class=\"body\">\n", title, !strcmp(client->uri, "/") ? " sel" : "", !strcmp(client->uri, "/supplies") ? " sel" : "", !strcmp(client->uri, "/media") ? " sel" : "");
+             "<div class=\"body\">\n", !strcmp(client->uri, "/") ? " sel" : "", !strcmp(client->uri, "/supplies") ? " sel" : "", !strcmp(client->uri, "/media") ? " sel" : "");
 }
 
 
@@ -3849,8 +3887,6 @@ load_legacy_attributes(
   {                                    /* media-source-supported values */
     "auto",
     "main",
-    "manual",
-    "by-pass-tray",                    /* AKA multi-purpose tray */
     "photo"
   };
   static const char * const media_type_supported[] =
@@ -3932,6 +3968,19 @@ load_legacy_attributes(
     IPP_QUALITY_NORMAL,
     IPP_QUALITY_HIGH
   };
+  static const char * const printer_input_tray[] =
+  {                                    /* printer-input-tray values */
+    "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=-2;level=-2;status=0;name=auto",
+    "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=250;level=100;status=0;name=main",
+    "type=sheetFeedManual;mediafeed=0;mediaxfeed=0;maxcapacity=1;level=-2;status=0;name=manual",
+    "type=sheetFeedAutoNonRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=25;level=-2;status=0;name=by-pass-tray"
+  };
+  static const char * const printer_input_tray_color[] =
+  {                                    /* printer-input-tray values */
+    "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=-2;level=-2;status=0;name=auto",
+    "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=250;level=-2;status=0;name=main",
+    "type=sheetFeedAutoRemovableTray;mediafeed=0;mediaxfeed=0;maxcapacity=25;level=-2;status=0;name=photo"
+  };
   static const char * const printer_supply[] =
   {                                    /* printer-supply values */
     "index=1;class=receptacleThatIsFilled;type=wasteToner;unit=percent;"
@@ -4124,7 +4173,7 @@ load_legacy_attributes(
   }
 
   /* media-col-ready */
-  attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "media-col-database", num_ready, NULL);
+  attr = ippAddCollections(attrs, IPP_TAG_PRINTER, "media-col-ready", num_ready, NULL);
   for (i = 0; i < num_ready; i ++)
   {
     int                bottom, left,           /* media-xxx-margins */
@@ -4314,6 +4363,20 @@ load_legacy_attributes(
   }
   ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-device-id", NULL, device_id);
 
+  /* printer-input-tray */
+  if (ppm_color > 0)
+  {
+    attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-input-tray", printer_input_tray_color[0], strlen(printer_input_tray_color[0]));
+    for (i = 1; i < (int)(sizeof(printer_input_tray_color) / sizeof(printer_input_tray_color[0])); i ++)
+      ippSetOctetString(attrs, &attr, i, printer_input_tray_color[i], strlen(printer_input_tray_color[i]));
+  }
+  else
+  {
+    attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-input-tray", printer_input_tray[0], strlen(printer_input_tray[0]));
+    for (i = 1; i < (int)(sizeof(printer_input_tray) / sizeof(printer_input_tray[0])); i ++)
+      ippSetOctetString(attrs, &attr, i, printer_input_tray[i], strlen(printer_input_tray[i]));
+  }
+
   /* printer-make-and-model */
   snprintf(make_model, sizeof(make_model), "%s %s", make, model);
   ippAddString(attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-make-and-model", NULL, make_model);
@@ -4336,7 +4399,7 @@ load_legacy_attributes(
   else
   {
     attr = ippAddOctetString(attrs, IPP_TAG_PRINTER, "printer-supply", printer_supply[0], strlen(printer_supply[0]));
-    for (i = 1; i < (int)(sizeof(printer_supply_color) / sizeof(printer_supply_color[0])); i ++)
+    for (i = 1; i < (int)(sizeof(printer_supply) / sizeof(printer_supply[0])); i ++)
       ippSetOctetString(attrs, &attr, i, printer_supply[i], strlen(printer_supply[i]));
 
     ippAddStrings(attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-supply-description", (int)(sizeof(printer_supply_description) / sizeof(printer_supply_description[0])), NULL, printer_supply_description);
@@ -4391,6 +4454,7 @@ load_legacy_attributes(
 }
 
 
+#if !CUPS_LITE
 /*
  * 'load_ppd_attributes()' - Load IPP attributes from a PPD file.
  */
@@ -4433,6 +4497,7 @@ load_ppd_attributes(
 
   return (NULL);
 }
+#endif /* !CUPS_LITE */
 
 
 /*
@@ -4761,30 +4826,43 @@ process_http(ippeve_client_t *client)   /* I - Client connection */
          * Send PNG icon file.
          */
 
-          int          fd;             /* Icon file */
-         struct stat   fileinfo;       /* Icon file information */
-         char          buffer[4096];   /* Copy buffer */
-         ssize_t       bytes;          /* Bytes */
+          if (client->printer->icon)
+          {
+           int         fd;             /* Icon file */
+           struct stat fileinfo;       /* Icon file information */
+           char        buffer[4096];   /* Copy buffer */
+           ssize_t     bytes;          /* Bytes */
 
-          fprintf(stderr, "Icon file is \"%s\".\n", client->printer->icon);
+           fprintf(stderr, "Icon file is \"%s\".\n", client->printer->icon);
 
-          if (!stat(client->printer->icon, &fileinfo) && (fd = open(client->printer->icon, O_RDONLY)) >= 0)
-         {
-           if (!respond_http(client, HTTP_STATUS_OK, NULL, "image/png", (size_t)fileinfo.st_size))
+           if (!stat(client->printer->icon, &fileinfo) && (fd = open(client->printer->icon, O_RDONLY)) >= 0)
            {
+             if (!respond_http(client, HTTP_STATUS_OK, NULL, "image/png", (size_t)fileinfo.st_size))
+             {
+               close(fd);
+               return (0);
+             }
+
+             while ((bytes = read(fd, buffer, sizeof(buffer))) > 0)
+               httpWrite2(client->http, buffer, (size_t)bytes);
+
+             httpFlushWrite(client->http);
+
              close(fd);
-             return (0);
            }
+           else
+             return (respond_http(client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0));
+         }
+         else
+         {
+           fputs("Icon file is internal printer.png.\n", stderr);
 
-           while ((bytes = read(fd, buffer, sizeof(buffer))) > 0)
-             httpWrite2(client->http, buffer, (size_t)bytes);
+           if (!respond_http(client, HTTP_STATUS_OK, NULL, "image/png", sizeof(printer_png)))
+             return (0);
 
+            httpWrite2(client->http, (const char *)printer_png, sizeof(printer_png));
            httpFlushWrite(client->http);
-
-           close(fd);
          }
-         else
-           return (respond_http(client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0));
        }
        else if (!strcmp(client->uri, "/"))
        {
@@ -4792,81 +4870,7 @@ process_http(ippeve_client_t *client)    /* I - Client connection */
          * Show web status page...
          */
 
-          ippeve_job_t *job;           /* Current job */
-         int           i;              /* Looping var */
-         ippeve_preason_t reason;      /* Current reason */
-         static const char * const reasons[] =
-         {                             /* Reason strings */
-           "Other",
-           "Cover Open",
-           "Input Tray Missing",
-           "Marker Supply Empty",
-           "Marker Supply Low",
-           "Marker Waste Almost Full",
-           "Marker Waste Full",
-           "Media Empty",
-           "Media Jam",
-           "Media Low",
-           "Media Needed",
-           "Moving to Paused",
-           "Paused",
-           "Spool Area Full",
-           "Toner Empty",
-           "Toner Low"
-         };
-
-          if (!respond_http(client, HTTP_STATUS_OK, encoding, "text/html", 0))
-           return (0);
-
-          html_header(client, client->printer->name);
-          html_printf(client,
-                     "<p><img align=\"right\" src=\"/icon.png\" width=\"64\" height=\"64\"><b>ippeveprinter (" CUPS_SVERSION ")</b></p>\n"
-                     "<p>%s, %d job(s).", client->printer->state == IPP_PSTATE_IDLE ? "Idle" : client->printer->state == IPP_PSTATE_PROCESSING ? "Printing" : "Stopped", cupsArrayCount(client->printer->jobs));
-         for (i = 0, reason = 1; i < (int)(sizeof(reasons) / sizeof(reasons[0])); i ++, reason <<= 1)
-           if (client->printer->state_reasons & reason)
-             html_printf(client, "\n<br>&nbsp;&nbsp;&nbsp;&nbsp;%s", reasons[i]);
-         html_printf(client, "</p>\n");
-
-          if (cupsArrayCount(client->printer->jobs) > 0)
-         {
-            _cupsRWLockRead(&(client->printer->rwlock));
-
-           html_printf(client, "<table class=\"striped\" summary=\"Jobs\"><thead><tr><th>Job #</th><th>Name</th><th>Owner</th><th>When</th></tr></thead><tbody>\n");
-           for (job = (ippeve_job_t *)cupsArrayFirst(client->printer->jobs); job; job = (ippeve_job_t *)cupsArrayNext(client->printer->jobs))
-           {
-             char      when[256],      /* When job queued/started/finished */
-                       hhmmss[64];     /* Time HH:MM:SS */
-
-              switch (job->state)
-             {
-               case IPP_JSTATE_PENDING :
-               case IPP_JSTATE_HELD :
-                   snprintf(when, sizeof(when), "Queued at %s", time_string(job->created, hhmmss, sizeof(hhmmss)));
-                   break;
-               case IPP_JSTATE_PROCESSING :
-               case IPP_JSTATE_STOPPED :
-                   snprintf(when, sizeof(when), "Started at %s", time_string(job->processing, hhmmss, sizeof(hhmmss)));
-                   break;
-               case IPP_JSTATE_ABORTED :
-                   snprintf(when, sizeof(when), "Aborted at %s", time_string(job->completed, hhmmss, sizeof(hhmmss)));
-                   break;
-               case IPP_JSTATE_CANCELED :
-                   snprintf(when, sizeof(when), "Canceled at %s", time_string(job->completed, hhmmss, sizeof(hhmmss)));
-                   break;
-               case IPP_JSTATE_COMPLETED :
-                   snprintf(when, sizeof(when), "Completed at %s", time_string(job->completed, hhmmss, sizeof(hhmmss)));
-                   break;
-             }
-
-             html_printf(client, "<tr><td>%d</td><td>%s</td><td>%s</td><td>%s</td></tr>\n", job->id, job->name, job->username, when);
-           }
-           html_printf(client, "</tbody></table>\n");
-
-           _cupsRWUnlock(&(client->printer->rwlock));
-         }
-          html_footer(client);
-
-         return (1);
+          return (show_status(client));
        }
        else if (!strcmp(client->uri, "/media"))
        {
@@ -4874,155 +4878,7 @@ process_http(ippeve_client_t *client)   /* I - Client connection */
          * Show web media page...
          */
 
-         int           num_options;    /* Number of form options */
-         cups_option_t *options;       /* Form options */
-#if 0
-         /* TODO: Update me */
-         int           i,              /* Looping var */
-          static const char * const sizes[] =
-         {                             /* Size strings */
-           "ISO A4",
-           "ISO A5",
-           "ISO A6",
-           "DL Envelope",
-           "US Legal",
-           "US Letter",
-           "#10 Envelope",
-           "3x5 Photo",
-           "3.5x5 Photo",
-           "4x6 Photo",
-           "5x7 Photo"
-         };
-         static const char * const types[] =
-                                         /* Type strings */
-         {
-           "Auto",
-           "Cardstock",
-           "Envelope",
-           "Labels",
-           "Other",
-           "Glossy Photo",
-           "High-Gloss Photo",
-           "Matte Photo",
-           "Satin Photo",
-           "Semi-Gloss Photo",
-           "Plain",
-           "Letterhead",
-           "Transparency"
-         };
-         static const int sheets[] =   /* Number of sheets */
-         {
-           250,
-           100,
-           25,
-           5,
-           0
-         };
-#endif /* 0 */
-
-          if (!respond_http(client, HTTP_STATUS_OK, encoding, "text/html", 0))
-           return (0);
-
-          html_header(client, client->printer->name);
-
-         if ((num_options = parse_options(client, &options)) > 0)
-         {
-          /*
-           * WARNING: A real printer/server implementation MUST NOT implement
-           * media updates via a GET request - GET requests are supposed to be
-           * idempotent (without side-effects) and we obviously are not
-           * authenticating access here.  This form is provided solely to
-           * enable testing and development!
-           */
-
-#if 0
-           /* TODO: UPDATE ME */
-           const char  *val;           /* Form value */
-
-           if ((val = cupsGetOption("main_size", num_options, options)) != NULL)
-             client->printer->main_size = atoi(val);
-           if ((val = cupsGetOption("main_type", num_options, options)) != NULL)
-             client->printer->main_type = atoi(val);
-           if ((val = cupsGetOption("main_level", num_options, options)) != NULL)
-             client->printer->main_level = atoi(val);
-
-           if ((val = cupsGetOption("envelope_size", num_options, options)) != NULL)
-             client->printer->envelope_size = atoi(val);
-           if ((val = cupsGetOption("envelope_level", num_options, options)) != NULL)
-             client->printer->envelope_level = atoi(val);
-
-           if ((val = cupsGetOption("photo_size", num_options, options)) != NULL)
-             client->printer->photo_size = atoi(val);
-           if ((val = cupsGetOption("photo_type", num_options, options)) != NULL)
-             client->printer->photo_type = atoi(val);
-           if ((val = cupsGetOption("photo_level", num_options, options)) != NULL)
-             client->printer->photo_level = atoi(val);
-
-            if ((client->printer->main_level < 100 && client->printer->main_level > 0) || (client->printer->envelope_level < 25 && client->printer->envelope_level > 0) || (client->printer->photo_level < 25 && client->printer->photo_level > 0))
-             client->printer->state_reasons |= IPPEVE_PREASON_MEDIA_LOW;
-           else
-             client->printer->state_reasons &= (ippeve_preason_t)~IPPEVE_PREASON_MEDIA_LOW;
-
-            if ((client->printer->main_level == 0 && client->printer->main_size > IPPEVE_MEDIA_SIZE_NONE) || (client->printer->envelope_level == 0 && client->printer->envelope_size > IPPEVE_MEDIA_SIZE_NONE) || (client->printer->photo_level == 0 && client->printer->photo_size > IPPEVE_MEDIA_SIZE_NONE))
-           {
-             client->printer->state_reasons |= IPPEVE_PREASON_MEDIA_EMPTY;
-             if (client->printer->active_job)
-               client->printer->state_reasons |= IPPEVE_PREASON_MEDIA_NEEDED;
-           }
-           else
-             client->printer->state_reasons &= (ippeve_preason_t)~(IPPEVE_PREASON_MEDIA_EMPTY | IPPEVE_PREASON_MEDIA_NEEDED);
-#endif /* 0 */
-
-           html_printf(client, "<blockquote>Media updated.</blockquote>\n");
-          }
-
-          html_printf(client, "<form method=\"GET\" action=\"/media\">\n");
-
-#if 0
-         /* TODO: UPDATE ME */
-          html_printf(client, "<table class=\"form\" summary=\"Media\">\n");
-          html_printf(client, "<tr><th>Main Tray:</th><td><select name=\"main_size\"><option value=\"-1\">None</option>");
-          for (i = 0; i < (int)(sizeof(sizes) / sizeof(sizes[0])); i ++)
-           if (!strstr(sizes[i], "Envelope") && !strstr(sizes[i], "Photo"))
-             html_printf(client, "<option value=\"%d\"%s>%s</option>", i, i == client->printer->main_size ? " selected" : "", sizes[i]);
-         html_printf(client, "</select> <select name=\"main_type\"><option value=\"-1\">None</option>");
-          for (i = 0; i < (int)(sizeof(types) / sizeof(types[0])); i ++)
-           if (!strstr(types[i], "Photo"))
-             html_printf(client, "<option value=\"%d\"%s>%s</option>", i, i == client->printer->main_type ? " selected" : "", types[i]);
-         html_printf(client, "</select> <select name=\"main_level\">");
-          for (i = 0; i < (int)(sizeof(sheets) / sizeof(sheets[0])); i ++)
-           html_printf(client, "<option value=\"%d\"%s>%d sheets</option>", sheets[i], sheets[i] == client->printer->main_level ? " selected" : "", sheets[i]);
-         html_printf(client, "</select></td></tr>\n");
-
-          html_printf(client,
-                     "<tr><th>Envelope Feeder:</th><td><select name=\"envelope_size\"><option value=\"-1\">None</option>");
-          for (i = 0; i < (int)(sizeof(sizes) / sizeof(sizes[0])); i ++)
-           if (strstr(sizes[i], "Envelope"))
-             html_printf(client, "<option value=\"%d\"%s>%s</option>", i, i == client->printer->envelope_size ? " selected" : "", sizes[i]);
-         html_printf(client, "</select> <select name=\"envelope_level\">");
-          for (i = 0; i < (int)(sizeof(sheets) / sizeof(sheets[0])); i ++)
-           html_printf(client, "<option value=\"%d\"%s>%d sheets</option>", sheets[i], sheets[i] == client->printer->envelope_level ? " selected" : "", sheets[i]);
-         html_printf(client, "</select></td></tr>\n");
-
-          html_printf(client,
-                     "<tr><th>Photo Tray:</th><td><select name=\"photo_size\"><option value=\"-1\">None</option>");
-          for (i = 0; i < (int)(sizeof(sizes) / sizeof(sizes[0])); i ++)
-           if (strstr(sizes[i], "Photo"))
-             html_printf(client, "<option value=\"%d\"%s>%s</option>", i, i == client->printer->photo_size ? " selected" : "", sizes[i]);
-         html_printf(client, "</select> <select name=\"photo_type\"><option value=\"-1\">None</option>");
-          for (i = 0; i < (int)(sizeof(types) / sizeof(types[0])); i ++)
-           if (strstr(types[i], "Photo"))
-             html_printf(client, "<option value=\"%d\"%s>%s</option>", i, i == client->printer->photo_type ? " selected" : "", types[i]);
-         html_printf(client, "</select> <select name=\"photo_level\">");
-          for (i = 0; i < (int)(sizeof(sheets) / sizeof(sheets[0])); i ++)
-           html_printf(client, "<option value=\"%d\"%s>%d sheets</option>", sheets[i], sheets[i] == client->printer->photo_level ? " selected" : "", sheets[i]);
-         html_printf(client, "</select></td></tr>\n");
-#endif /* 0 */
-
-         html_printf(client, "<tr><td></td><td><input type=\"submit\" value=\"Update Media\"></td></tr></table></form>\n");
-          html_footer(client);
-
-         return (1);
+          return (show_media(client));
        }
        else if (!strcmp(client->uri, "/supplies"))
        {
@@ -5030,82 +4886,7 @@ process_http(ippeve_client_t *client)    /* I - Client connection */
          * Show web supplies page...
          */
 
-          int                  num_options;    /* Number of form options */
-         cups_option_t *options;       /* Form options */
-#if 0
-          /* TODO: UPDATE ME */
-          int          i, j;           /* Looping vars */
-         static const int levels[] = { 0, 5, 10, 25, 50, 75, 90, 95, 100 };
-#endif /* 0 */
-
-          if (!respond_http(client, HTTP_STATUS_OK, encoding, "text/html", 0))
-           return (0);
-
-          html_header(client, client->printer->name);
-
-         if ((num_options = parse_options(client, &options)) > 0)
-         {
-          /*
-           * WARNING: A real printer/server implementation MUST NOT implement
-           * supply updates via a GET request - GET requests are supposed to be
-           * idempotent (without side-effects) and we obviously are not
-           * authenticating access here.  This form is provided solely to
-           * enable testing and development!
-           */
-
-//         char        name[64];       /* Form field */
-//         const char  *val;           /* Form value */
-
-            client->printer->state_reasons &= (ippeve_preason_t)~(IPPEVE_PREASON_MARKER_SUPPLY_EMPTY | IPPEVE_PREASON_MARKER_SUPPLY_LOW | IPPEVE_PREASON_MARKER_WASTE_ALMOST_FULL | IPPEVE_PREASON_MARKER_WASTE_FULL | IPPEVE_PREASON_TONER_EMPTY | IPPEVE_PREASON_TONER_LOW);
-
-#if 0
-           /* TODO: UPDATE ME */
-           for (i = 0; i < (int)(sizeof(printer_supplies) / sizeof(printer_supplies[0])); i ++)
-           {
-             snprintf(name, sizeof(name), "supply_%d", i);
-             if ((val = cupsGetOption(name, num_options, options)) != NULL)
-             {
-               int level = client->printer->supplies[i] = atoi(val);
-                                       /* New level */
-
-               if (i < 4)
-               {
-                 if (level == 0)
-                   client->printer->state_reasons |= IPPEVE_PREASON_TONER_EMPTY;
-                 else if (level < 10)
-                   client->printer->state_reasons |= IPPEVE_PREASON_TONER_LOW;
-               }
-               else
-               {
-                 if (level == 100)
-                   client->printer->state_reasons |= IPPEVE_PREASON_MARKER_WASTE_FULL;
-                 else if (level > 90)
-                   client->printer->state_reasons |= IPPEVE_PREASON_MARKER_WASTE_ALMOST_FULL;
-               }
-             }
-            }
-#endif /* 0 */
-
-           html_printf(client, "<blockquote>Supplies updated.</blockquote>\n");
-          }
-
-          html_printf(client, "<form method=\"GET\" action=\"/supplies\">\n");
-
-         html_printf(client, "<table class=\"form\" summary=\"Supplies\">\n");
-#if 0
-          /* TODO: UPDATE ME */
-         for (i = 0; i < (int)(sizeof(printer_supplies) / sizeof(printer_supplies[0])); i ++)
-         {
-           html_printf(client, "<tr><th>%s:</th><td><select name=\"supply_%d\">", printer_supplies[i], i);
-           for (j = 0; j < (int)(sizeof(levels) / sizeof(levels[0])); j ++)
-             html_printf(client, "<option value=\"%d\"%s>%d%%</option>", levels[j], levels[j] == client->printer->supplies[i] ? " selected" : "", levels[j]);
-           html_printf(client, "</select></td></tr>\n");
-         }
-#endif /* 0 */
-         html_printf(client, "<tr><td></td><td><input type=\"submit\" value=\"Update Supplies\"></td></tr>\n</table>\n</form>\n");
-          html_footer(client);
-
-         return (1);
+          return (show_supplies(client));
        }
        else
          return (respond_http(client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0));
@@ -5432,6 +5213,7 @@ process_job(ippeve_job_t *job)            /* I - Job */
     char               val[1280],      /* IPP_NAME=value */
                        *valptr;        /* Pointer into string */
 #ifndef _WIN32
+    int                        mystdout = -1;  /* File for stdout */
     int                        mypipe[2];      /* Pipe for stderr */
     char               line[2048],     /* Line from stderr */
                        *ptr,           /* Pointer into line */
@@ -5471,8 +5253,10 @@ process_job(ippeve_job_t *job)           /* I - Job */
     if (job->printer->device_uri && asprintf(myenvp + myenvc, "DEVICE_URI=%s", job->printer->device_uri) > 0)
       myenvc ++;
 
+#if !CUPS_LITE
     if (job->printer->ppdfile && asprintf(myenvp + myenvc, "PPD=%s", job->printer->ppdfile) > 0)
       myenvc ++;
+#endif /* !CUPS_LITE */
 
     if ((attr = ippFindAttribute(job->attrs, "document-name", IPP_TAG_NAME)) != NULL && asprintf(myenvp + myenvc, "DOCUMENT_NAME=%s", ippGetString(attr, 0, NULL)) > 0)
       myenvc ++;
@@ -5562,6 +5346,75 @@ process_job(ippeve_job_t *job)           /* I - Job */
     status = _spawnvpe(_P_WAIT, job->printer->command, myargv, myenvp);
 
 #else
+    if (job->printer->device_uri)
+    {
+      char     scheme[32],             /* URI scheme */
+               userpass[256],          /* username:password (unused) */
+               host[256],              /* Hostname or IP address */
+               resource[256];          /* Resource path */
+      int      port;                   /* Port number */
+
+
+      if (httpSeparateURI(HTTP_URI_CODING_ALL, job->printer->device_uri, scheme, sizeof(scheme), userpass, sizeof(userpass), host, sizeof(host), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
+      {
+        fprintf(stderr, "Bad device URI \"%s\".\n", job->printer->device_uri);
+      }
+      else if (!strcmp(scheme, "file"))
+      {
+        struct stat    fileinfo;       /* See if this is a file or directory... */
+
+        if (stat(resource, &fileinfo))
+        {
+          if (errno == ENOENT)
+          {
+            if ((mystdout = open(resource, O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0)
+             fprintf(stderr, "Unable to create \"%s\": %s\n", resource, strerror(errno));
+          }
+          else
+            fprintf(stderr, "Unable to access \"%s\": %s\n", resource, strerror(errno));
+        }
+        else if (S_ISDIR(fileinfo.st_mode))
+        {
+          snprintf(line, sizeof(line), "%s/%d.prn", resource, job->id);
+
+         if ((mystdout = open(line, O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0)
+            fprintf(stderr, "Unable to create \"%s\": %s\n", line, strerror(errno));
+        }
+       else if (!S_ISREG(fileinfo.st_mode))
+       {
+         if ((mystdout = open(resource, O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0)
+            fprintf(stderr, "Unable to create \"%s\": %s\n", resource, strerror(errno));
+       }
+        else if ((mystdout = open(resource, O_WRONLY)) < 0)
+         fprintf(stderr, "Unable to open \"%s\": %s\n", resource, strerror(errno));
+      }
+      else if (!strcmp(scheme, "socket"))
+      {
+        http_addrlist_t        *addrlist;      /* List of addresses */
+        char           service[32];    /* Service number */
+
+        snprintf(service, sizeof(service), "%d", port);
+
+        if ((addrlist = httpAddrGetList(host, AF_UNSPEC, service)) == NULL)
+          fprintf(stderr, "Unable to find \"%s\": %s\n", host, cupsLastErrorString());
+        else if (!httpAddrConnect2(addrlist, &mystdout, 30000, &(job->cancel)))
+          fprintf(stderr, "Unable to connect to \"%s\": %s\n", host, cupsLastErrorString());
+
+        httpAddrFreeList(addrlist);
+      }
+      else
+      {
+        fprintf(stderr, "Unsupported device URI scheme \"%s\".\n", scheme);
+      }
+    }
+    else if ((mystdout = create_job_file(job->printer, job, line, sizeof(line), "prn")) >= 0)
+    {
+      fprintf(stderr, "Saving print command output to \"%s\".\n", line);
+    }
+
+    if (mystdout < 0)
+      mystdout = open("/dev/null", O_WRONLY);
+
     if (pipe(mypipe))
     {
       perror("Unable to create pipe for stderr");
@@ -5574,6 +5427,10 @@ process_job(ippeve_job_t *job)           /* I - Job */
       * Child comes here...
       */
 
+      close(1);
+      dup2(mystdout, 1);
+      close(mystdout);
+
       close(2);
       dup2(mypipe[1], 2);
       close(mypipe[0]);
@@ -5591,6 +5448,7 @@ process_job(ippeve_job_t *job)            /* I - Job */
       perror("Unable to start job processing command");
       status = -1;
 
+      close(mystdout);
       close(mypipe[0]);
       close(mypipe[1]);
 
@@ -5610,6 +5468,12 @@ process_job(ippeve_job_t *job)           /* I - Job */
       while (myenvc > 0)
        free(myenvp[-- myenvc]);
 
+     /*
+      * Close the output file in the parent process...
+      */
+
+      close(mystdout);
+
      /*
       * If the pipe exists, read from it until EOF...
       */
@@ -6309,6 +6173,535 @@ run_printer(ippeve_printer_t *printer)  /* I - Printer */
 }
 
 
+/*
+ * 'show_media()' - Show media load state.
+ */
+
+static int                             /* O - 1 on success, 0 on failure */
+show_media(ippeve_client_t  *client)   /* I - Client connection */
+{
+  ippeve_printer_t *printer = client->printer;
+                                       /* Printer */
+  int                  i, j,           /* Looping vars */
+                        num_ready,     /* Number of ready media */
+                        num_sizes,     /* Number of media sizes */
+                       num_sources,    /* Number of media sources */
+                        num_types;     /* Number of media types */
+  ipp_attribute_t      *media_col_ready,/* media-col-ready attribute */
+                        *media_ready,  /* media-ready attribute */
+                        *media_sizes,  /* media-supported attribute */
+                        *media_sources,        /* media-source-supported attribute */
+                        *media_types,  /* media-type-supported attribute */
+                        *input_tray;   /* printer-input-tray attribute */
+  ipp_t                        *media_col;     /* media-col value */
+  const char            *media_size,   /* media value */
+                        *media_source, /* media-source value */
+                        *media_type,   /* media-type value */
+                        *ready_size,   /* media-col-ready media-size[-name] value */
+                        *ready_source, /* media-col-ready media-source value */
+                        *ready_tray,   /* printer-input-tray value */
+                        *ready_type;   /* media-col-ready media-type value */
+  char                 tray_str[1024], /* printer-input-tray string value */
+                       *tray_ptr;      /* Pointer into value */
+  int                  tray_len;       /* Length of printer-input-tray value */
+  int                  ready_sheets;   /* printer-input-tray sheets value */
+  int                  num_options;    /* Number of form options */
+  cups_option_t                *options;       /* Form options */
+  static const int     sheets[] =      /* Number of sheets */
+  {
+    250,
+    100,
+    25,
+    5,
+    0,
+    -2
+  };
+
+
+  if (!respond_http(client, HTTP_STATUS_OK, NULL, "text/html", 0))
+    return (0);
+
+  html_header(client, printer->name, 0);
+
+  if ((media_col_ready = ippFindAttribute(printer->attrs, "media-col-ready", IPP_TAG_BEGIN_COLLECTION)) == NULL)
+  {
+    html_printf(client, "<p>Error: No media-col-ready defined for printer.</p>\n");
+    html_footer(client);
+    return (1);
+  }
+
+  media_ready = ippFindAttribute(printer->attrs, "media-ready", IPP_TAG_ZERO);
+
+  if ((media_sizes = ippFindAttribute(printer->attrs, "media-supported", IPP_TAG_ZERO)) == NULL)
+  {
+    html_printf(client, "<p>Error: No media-supported defined for printer.</p>\n");
+    html_footer(client);
+    return (1);
+  }
+
+  if ((media_sources = ippFindAttribute(printer->attrs, "media-source-supported", IPP_TAG_ZERO)) == NULL)
+  {
+    html_printf(client, "<p>Error: No media-source-supported defined for printer.</p>\n");
+    html_footer(client);
+    return (1);
+  }
+
+  if ((media_types = ippFindAttribute(printer->attrs, "media-type-supported", IPP_TAG_ZERO)) == NULL)
+  {
+    html_printf(client, "<p>Error: No media-type-supported defined for printer.</p>\n");
+    html_footer(client);
+    return (1);
+  }
+
+  if ((input_tray = ippFindAttribute(printer->attrs, "printer-input-tray", IPP_TAG_STRING)) == NULL)
+  {
+    html_printf(client, "<p>Error: No printer-input-tray defined for printer.</p>\n");
+    html_footer(client);
+    return (1);
+  }
+
+  num_ready   = ippGetCount(media_col_ready);
+  num_sizes   = ippGetCount(media_sizes);
+  num_sources = ippGetCount(media_sources);
+  num_types   = ippGetCount(media_types);
+
+  if (num_sources != ippGetCount(input_tray))
+  {
+    html_printf(client, "<p>Error: Different number of trays in media-source-supported and printer-input-tray defined for printer.</p>\n");
+    html_footer(client);
+    return (1);
+  }
+
+ /*
+  * Process form data if present...
+  */
+
+  if ((num_options = parse_options(client, &options)) > 0)
+  {
+   /*
+    * WARNING: A real printer/server implementation MUST NOT implement
+    * media updates via a GET request - GET requests are supposed to be
+    * idempotent (without side-effects) and we obviously are not
+    * authenticating access here.  This form is provided solely to
+    * enable testing and development!
+    */
+
+    char       name[255];              /* Form name */
+    const char *val;                   /* Form value */
+    pwg_media_t        *media;                 /* Media info */
+
+    _cupsRWLockWrite(&printer->rwlock);
+
+    ippDeleteAttribute(printer->attrs, media_col_ready);
+    media_col_ready = NULL;
+
+    if (media_ready)
+    {
+      ippDeleteAttribute(printer->attrs, media_ready);
+      media_ready = NULL;
+    }
+
+    printer->state_reasons &= (ippeve_preason_t)~(IPPEVE_PREASON_MEDIA_LOW | IPPEVE_PREASON_MEDIA_EMPTY | IPPEVE_PREASON_MEDIA_NEEDED);
+
+    for (i = 0; i < num_sources; i ++)
+    {
+      media_source = ippGetString(media_sources, i, NULL);
+
+      if (!strcmp(media_source, "auto") || !strcmp(media_source, "manual"))
+       continue;
+
+      snprintf(name, sizeof(name), "size%d", i);
+      if ((media_size = cupsGetOption(name, num_options, options)) != NULL && (media = pwgMediaForPWG(media_size)) != NULL)
+      {
+        snprintf(name, sizeof(name), "type%d", i);
+        if ((media_type = cupsGetOption(name, num_options, options)) != NULL && !*media_type)
+          media_type = NULL;
+
+        if (media_ready)
+          ippSetString(printer->attrs, &media_ready, ippGetCount(media_ready), media_size);
+        else
+          media_ready = ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-ready", NULL, media_size);
+
+        media_col = create_media_col(media_size, media_source, media_type, media->width, media->length, -1, -1, -1, -1);
+
+        if (media_col_ready)
+          ippSetCollection(printer->attrs, &media_col_ready, ippGetCount(media_col_ready), media_col);
+        else
+          media_col_ready = ippAddCollection(printer->attrs, IPP_TAG_PRINTER, "media-col-ready", media_col);
+        ippDelete(media_col);
+      }
+      else
+        media = NULL;
+
+      snprintf(name, sizeof(name), "level%d", i);
+      if ((val = cupsGetOption(name, num_options, options)) != NULL)
+        ready_sheets = atoi(val);
+      else
+        ready_sheets = 0;
+
+      snprintf(tray_str, sizeof(tray_str), "type=sheetFeedAuto%sRemovableTray;mediafeed=%d;mediaxfeed=%d;maxcapacity=%d;level=%d;status=0;name=%s;", !strcmp(media_source, "by-pass-tray") ? "Non" : "", media ? media->length : 0, media ? media->width : 0, !strcmp(media_source, "main") ? 250 : 25, ready_sheets, media_source);
+
+      ippSetOctetString(printer->attrs, &input_tray, i, tray_str, (int)strlen(tray_str));
+
+      if (ready_sheets == 0)
+      {
+        printer->state_reasons |= IPPEVE_PREASON_MEDIA_EMPTY;
+        if (printer->active_job)
+          printer->state_reasons |= IPPEVE_PREASON_MEDIA_NEEDED;
+      }
+      else if (ready_sheets < 25 && ready_sheets > 0)
+        printer->state_reasons |= IPPEVE_PREASON_MEDIA_LOW;
+    }
+
+    if (!media_col_ready)
+      media_col_ready = ippAddOutOfBand(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NOVALUE, "media-col-ready");
+
+    if (!media_ready)
+      media_ready = ippAddOutOfBand(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NOVALUE, "media-ready");
+
+    _cupsRWUnlock(&printer->rwlock);
+
+    html_printf(client, "<blockquote>Media updated.</blockquote>\n");
+  }
+
+  html_printf(client, "<form method=\"GET\" action=\"/media\">\n");
+
+  html_printf(client, "<table class=\"form\" summary=\"Media\">\n");
+  for (i = 0; i < num_sources; i ++)
+  {
+    media_source = ippGetString(media_sources, i, NULL);
+
+    if (!strcmp(media_source, "auto") || !strcmp(media_source, "manual"))
+      continue;
+
+    for (j = 0, ready_size = NULL, ready_type = NULL; j < num_ready; j ++)
+    {
+      media_col    = ippGetCollection(media_col_ready, j);
+      ready_size   = ippGetString(ippFindAttribute(media_col, "media-size-name", IPP_TAG_ZERO), 0, NULL);
+      ready_source = ippGetString(ippFindAttribute(media_col, "media-source", IPP_TAG_ZERO), 0, NULL);
+      ready_type   = ippGetString(ippFindAttribute(media_col, "media-type", IPP_TAG_ZERO), 0, NULL);
+
+      if (ready_source && !strcmp(ready_source, media_source))
+        break;
+
+      ready_source = NULL;
+      ready_size   = NULL;
+      ready_type   = NULL;
+    }
+
+   /*
+    * Media size...
+    */
+
+    html_printf(client, "<tr><th>%s:</th><td><select name=\"size%d\"><option value=\"\">None</option>", media_source, i);
+    for (j = 0; j < num_sizes; j ++)
+    {
+      media_size = ippGetString(media_sizes, j, NULL);
+
+      html_printf(client, "<option%s>%s</option>", (ready_size && !strcmp(ready_size, media_size)) ? " selected" : "", media_size);
+    }
+    html_printf(client, "</select>\n");
+
+   /*
+    * Media type...
+    */
+
+    html_printf(client, "<select name=\"type%d\"><option value=\"\">None</option>", i);
+    for (j = 0; j < num_types; j ++)
+    {
+      media_type = ippGetString(media_types, j, NULL);
+
+      html_printf(client, "<option%s>%s</option>", (ready_type && !strcmp(ready_type, media_type)) ? " selected" : "", media_type);
+    }
+    html_printf(client, "</select>\n");
+
+   /*
+    * Level/sheets loaded...
+    */
+
+    if ((ready_tray = ippGetOctetString(input_tray, i, &tray_len)) != NULL)
+    {
+      if (tray_len > (sizeof(tray_str) - 1))
+        tray_len = sizeof(tray_str) - 1;
+      memcpy(tray_str, ready_tray, (size_t)tray_len);
+      tray_str[tray_len] = '\0';
+
+      if ((tray_ptr = strstr(tray_str, "level=")) != NULL)
+        ready_sheets = atoi(tray_ptr + 6);
+      else
+        ready_sheets = 0;
+    }
+    else
+      ready_sheets = 0;
+
+    html_printf(client, "<select name=\"level%d\">", i);
+    for (j = 0; j < (int)(sizeof(sheets) / sizeof(sheets[0])); j ++)
+    {
+      if (strcmp(media_source, "main") && sheets[j] > 25)
+        continue;
+
+      if (sheets[j] < 0)
+       html_printf(client, "<option value=\"%d\"%s>Unknown</option>", sheets[j], sheets[j] == ready_sheets ? " selected" : "");
+      else
+       html_printf(client, "<option value=\"%d\"%s>%d sheets</option>", sheets[j], sheets[j] == ready_sheets ? " selected" : "", sheets[j]);
+    }
+    html_printf(client, "</select></td></tr>\n");
+  }
+
+  html_printf(client, "<tr><td></td><td><input type=\"submit\" value=\"Update Media\"></td></tr></table></form>\n");
+  html_footer(client);
+
+  return (1);
+}
+
+
+/*
+ * 'show_status()' - Show printer/system state.
+ */
+
+static int                             /* O - 1 on success, 0 on failure */
+show_status(ippeve_client_t  *client)  /* I - Client connection */
+{
+  ippeve_printer_t *printer = client->printer;
+                                       /* Printer */
+  ippeve_job_t         *job;           /* Current job */
+  int                  i;              /* Looping var */
+  ippeve_preason_t     reason;         /* Current reason */
+  static const char * const reasons[] =        /* Reason strings */
+  {
+    "Other",
+    "Cover Open",
+    "Input Tray Missing",
+    "Marker Supply Empty",
+    "Marker Supply Low",
+    "Marker Waste Almost Full",
+    "Marker Waste Full",
+    "Media Empty",
+    "Media Jam",
+    "Media Low",
+    "Media Needed",
+    "Moving to Paused",
+    "Paused",
+    "Spool Area Full",
+    "Toner Empty",
+    "Toner Low"
+  };
+  static const char * const state_colors[] =
+  {                                    /* State colors */
+    "#0C0",                            /* Idle */
+    "#EE0",                            /* Processing */
+    "#C00"                             /* Stopped */
+  };
+
+
+  if (!respond_http(client, HTTP_STATUS_OK, NULL, "text/html", 0))
+    return (0);
+
+  html_header(client, printer->name, printer->state == IPP_PSTATE_PROCESSING ? 5 : 15);
+  html_printf(client, "<h1><img style=\"background: %s; border-radius: 10px; float: left; margin-right: 10px; padding: 10px;\" src=\"/icon.png\" width=\"64\" height=\"64\">%s Jobs</h1>\n", state_colors[printer->state - IPP_PSTATE_IDLE], printer->name);
+  html_printf(client, "<p>%s, %d job(s).", printer->state == IPP_PSTATE_IDLE ? "Idle" : printer->state == IPP_PSTATE_PROCESSING ? "Printing" : "Stopped", cupsArrayCount(printer->jobs));
+  for (i = 0, reason = 1; i < (int)(sizeof(reasons) / sizeof(reasons[0])); i ++, reason <<= 1)
+    if (printer->state_reasons & reason)
+      html_printf(client, "\n<br>&nbsp;&nbsp;&nbsp;&nbsp;%s", reasons[i]);
+  html_printf(client, "</p>\n");
+
+  if (cupsArrayCount(printer->jobs) > 0)
+  {
+    _cupsRWLockRead(&(printer->rwlock));
+
+    html_printf(client, "<table class=\"striped\" summary=\"Jobs\"><thead><tr><th>Job #</th><th>Name</th><th>Owner</th><th>Status</th></tr></thead><tbody>\n");
+    for (job = (ippeve_job_t *)cupsArrayFirst(printer->jobs); job; job = (ippeve_job_t *)cupsArrayNext(printer->jobs))
+    {
+      char     when[256],              /* When job queued/started/finished */
+             hhmmss[64];               /* Time HH:MM:SS */
+
+      switch (job->state)
+      {
+       case IPP_JSTATE_PENDING :
+       case IPP_JSTATE_HELD :
+           snprintf(when, sizeof(when), "Queued at %s", time_string(job->created, hhmmss, sizeof(hhmmss)));
+           break;
+       case IPP_JSTATE_PROCESSING :
+       case IPP_JSTATE_STOPPED :
+           snprintf(when, sizeof(when), "Started at %s", time_string(job->processing, hhmmss, sizeof(hhmmss)));
+           break;
+       case IPP_JSTATE_ABORTED :
+           snprintf(when, sizeof(when), "Aborted at %s", time_string(job->completed, hhmmss, sizeof(hhmmss)));
+           break;
+       case IPP_JSTATE_CANCELED :
+           snprintf(when, sizeof(when), "Canceled at %s", time_string(job->completed, hhmmss, sizeof(hhmmss)));
+           break;
+       case IPP_JSTATE_COMPLETED :
+           snprintf(when, sizeof(when), "Completed at %s", time_string(job->completed, hhmmss, sizeof(hhmmss)));
+           break;
+      }
+
+      html_printf(client, "<tr><td>%d</td><td>%s</td><td>%s</td><td>%s</td></tr>\n", job->id, job->name, job->username, when);
+    }
+    html_printf(client, "</tbody></table>\n");
+
+    _cupsRWUnlock(&(printer->rwlock));
+  }
+
+  html_footer(client);
+
+  return (1);
+}
+
+
+/*
+ * 'show_supplies()' - Show printer supplies.
+ */
+
+static int                             /* O - 1 on success, 0 on failure */
+show_supplies(
+    ippeve_client_t  *client)          /* I - Client connection */
+{
+  ippeve_printer_t *printer = client->printer;
+                                       /* Printer */
+  int          i,                      /* Looping var */
+               num_supply;             /* Number of supplies */
+  ipp_attribute_t *supply,             /* printer-supply attribute */
+               *supply_desc;           /* printer-supply-description attribute */
+  int          num_options;            /* Number of form options */
+  cups_option_t        *options;               /* Form options */
+  int          supply_len,             /* Length of supply value */
+               level;                  /* Supply level */
+  const char   *supply_value;          /* Supply value */
+  char         supply_text[1024],      /* Supply string */
+               *supply_ptr;            /* Pointer into supply string */
+  static const char * const printer_supply[] =
+  {                                    /* printer-supply values */
+    "index=1;class=receptacleThatIsFilled;type=wasteToner;unit=percent;"
+        "maxcapacity=100;level=%d;colorantname=unknown;",
+    "index=2;class=supplyThatIsConsumed;type=toner;unit=percent;"
+        "maxcapacity=100;level=%d;colorantname=black;",
+    "index=3;class=supplyThatIsConsumed;type=toner;unit=percent;"
+        "maxcapacity=100;level=%d;colorantname=cyan;",
+    "index=4;class=supplyThatIsConsumed;type=toner;unit=percent;"
+        "maxcapacity=100;level=%d;colorantname=magenta;",
+    "index=5;class=supplyThatIsConsumed;type=toner;unit=percent;"
+        "maxcapacity=100;level=%d;colorantname=yellow;"
+  };
+  static const char * const colors[] = /* Colors for the supply-level bars */
+  {
+    "#777 linear-gradient(#333,#777)",
+    "#000 linear-gradient(#666,#000)",
+    "#0FF linear-gradient(#6FF,#0FF)",
+    "#F0F linear-gradient(#F6F,#F0F)",
+    "#CC0 linear-gradient(#EE6,#EE0)"
+  };
+
+
+  if (!respond_http(client, HTTP_STATUS_OK, NULL, "text/html", 0))
+    return (0);
+
+  html_header(client, printer->name, 0);
+
+  if ((supply = ippFindAttribute(printer->attrs, "printer-supply", IPP_TAG_STRING)) == NULL)
+  {
+    html_printf(client, "<p>Error: No printer-supply defined for printer.</p>\n");
+    html_footer(client);
+    return (1);
+  }
+
+  num_supply = ippGetCount(supply);
+
+  if ((supply_desc = ippFindAttribute(printer->attrs, "printer-supply-description", IPP_TAG_TEXT)) == NULL)
+  {
+    html_printf(client, "<p>Error: No printer-supply-description defined for printer.</p>\n");
+    html_footer(client);
+    return (1);
+  }
+
+  if (num_supply != ippGetCount(supply_desc))
+  {
+    html_printf(client, "<p>Error: Different number of values for printer-supply and printer-supply-description defined for printer.</p>\n");
+    html_footer(client);
+    return (1);
+  }
+
+  if ((num_options = parse_options(client, &options)) > 0)
+  {
+   /*
+    * WARNING: A real printer/server implementation MUST NOT implement
+    * supply updates via a GET request - GET requests are supposed to be
+    * idempotent (without side-effects) and we obviously are not
+    * authenticating access here.  This form is provided solely to
+    * enable testing and development!
+    */
+
+    char       name[64];               /* Form field */
+    const char *val;                   /* Form value */
+
+    _cupsRWLockWrite(&printer->rwlock);
+
+    ippDeleteAttribute(printer->attrs, supply);
+    supply = NULL;
+
+    printer->state_reasons &= (ippeve_preason_t)~(IPPEVE_PREASON_MARKER_SUPPLY_EMPTY | IPPEVE_PREASON_MARKER_SUPPLY_LOW | IPPEVE_PREASON_MARKER_WASTE_ALMOST_FULL | IPPEVE_PREASON_MARKER_WASTE_FULL | IPPEVE_PREASON_TONER_EMPTY | IPPEVE_PREASON_TONER_LOW);
+
+    for (i = 0; i < num_supply; i ++)
+    {
+      snprintf(name, sizeof(name), "supply%d", i);
+      if ((val = cupsGetOption(name, num_options, options)) != NULL)
+      {
+        level = atoi(val);      /* New level */
+
+        snprintf(supply_text, sizeof(supply_text), printer_supply[i], level);
+        if (supply)
+          ippSetOctetString(printer->attrs, &supply, ippGetCount(supply), supply_text, (int)strlen(supply_text));
+        else
+          supply = ippAddOctetString(printer->attrs, IPP_TAG_PRINTER, "printer-supply", supply_text, (int)strlen(supply_text));
+
+        if (i == 0)
+        {
+          if (level == 100)
+            printer->state_reasons |= IPPEVE_PREASON_MARKER_WASTE_FULL;
+          else if (level > 90)
+            printer->state_reasons |= IPPEVE_PREASON_MARKER_WASTE_ALMOST_FULL;
+        }
+        else
+        {
+          if (level == 0)
+            printer->state_reasons |= IPPEVE_PREASON_TONER_EMPTY;
+          else if (level < 10)
+            printer->state_reasons |= IPPEVE_PREASON_TONER_LOW;
+        }
+      }
+    }
+
+    _cupsRWUnlock(&printer->rwlock);
+
+    html_printf(client, "<blockquote>Supplies updated.</blockquote>\n");
+  }
+
+  html_printf(client, "<form method=\"GET\" action=\"/supplies\">\n");
+
+  html_printf(client, "<table class=\"form\" summary=\"Supplies\">\n");
+  for (i = 0; i < num_supply; i ++)
+  {
+    supply_value = ippGetOctetString(supply, i, &supply_len);
+    if (supply_len > (sizeof(supply_text) - 1))
+      supply_len = sizeof(supply_text) - 1;
+
+    memcpy(supply_text, supply_value, (size_t)supply_len);
+    supply_text[supply_len] = '\0';
+
+    if ((supply_ptr = strstr(supply_text, "level=")) != NULL)
+      level = atoi(supply_ptr + 6);
+    else
+      level = 50;
+
+    html_printf(client, "<tr><th>%s:</th><td><input name=\"supply%d\" size=\"3\" value=\"%d\"><span class=\"bar\" style=\"background: %s; width: %dpx;\"></span></td></tr>\n", ippGetString(supply_desc, i, NULL), i, level, colors[i], level * 2);
+  }
+  html_printf(client, "<tr><td></td><td><input type=\"submit\" value=\"Update Supplies\"></td></tr>\n</table>\n</form>\n");
+  html_footer(client);
+
+  return (1);
+}
+
+
 /*
  * 'time_string()' - Return the local time in hours, minutes, and seconds.
  */