xprintutil.c   [plain text]


/******************************************************************************
 ******************************************************************************
 **
 ** (c) Copyright 2001-2004 Roland Mainz <roland.mainz@nrubsig.org>
 ** 
 ** Permission is hereby granted, free of charge, to any person obtaining a copy
 ** of this software and associated documentation files (the "Software"), to deal
 ** in the Software without restriction, including without limitation the rights
 ** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 ** copies of the Software, and to permit persons to whom the Software is
 ** furnished to do so, subject to the following conditions:
 **
 ** The above copyright notice and this permission notice shall be included in
 ** all copies or substantial portions of the Software.
 **
 ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 ** COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 ** IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 ** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 **
 ** Except as contained in this notice, the names of the copyright holders shall
 ** not be used in advertising or otherwise to promote the sale, use or other
 ** dealings in this Software without prior written authorization from said
 ** copyright holders.
 **
 ******************************************************************************
 *****************************************************************************/

#include "xprintutil.h"
 
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include <limits.h>
#include <errno.h>
#include <locale.h>

#ifdef XPU_USE_NSPR
#include "plstr.h"
#undef strtok_r
#define strtok_r(s1, s2, x) PL_strtok_r((s1), (s2), (x))
#endif /* XPU_USE_NSPR */

/* List of tokens which can be used to separate entries in the 
 * $XPSERVERLIST env var */
static const char XPServerListSeparators[] = " \t\v\n\r\f";

/* conformace only; X11 API does (currrently) not make use of |const|. 
 * If Xlib API gets fixed these macros can be turned into empty 
 * placeholders... (|#define MAKE_STRING_WRITABLE(x)|) :-)
 */
#define MAKE_STRING_WRITABLE(str) (((str)?((str) = strdup(str)):0))
#define FREE_WRITABLE_STRING(str) free((void *)(str))
#define STRING_AS_WRITABLE(str) ((char *)(str))

/* Local prototypes */
static const char *XpuGetDefaultXpPrintername(void);
static const char *XpuGetXpServerList( void );
static const char *XpuEnumerateXpAttributeValue( const char *value, void **vcptr );
static const char *XpuGetCurrentAttributeGroup( void **vcptr );
static void XpuDisposeEnumerateXpAttributeValue( void **vc );
static Bool XpuEnumerateMediumSourceSizes( Display *pdpy, XPContext pcontext,
                                           const char **tray_name,
                                           const char **medium_name, int *mbool, 
                                           float *ma1, float *ma2, float *ma3, float *ma4,
                                           void **vcptr );
static void XpuDisposeEnumerateMediumSourceSizes( void **vc );

/*
** XprintUtil functions start with Xpu
**
*/

int XpuCheckExtension( Display *pdpy )
{
  char *display = XDisplayString(pdpy);
  short major = 0,
        minor = 0;

  if( XpQueryVersion(pdpy, &major, &minor) != 0 )
  {
    XPU_DEBUG_ONLY(printf("XpuCheckExtension: XpQueryVersion '%s' %d %d\n", XPU_NULLXSTR(display), (int)major, (int)minor));
    return(1);
  }
  else
  {
    XPU_DEBUG_ONLY(printf("XpuCheckExtension: XpQueryVersion '%s' returned 0(=Xprint not supported)\n", XPU_NULLXSTR(display)));
  }
  
  return(0);
}

/* Get the default printer name from the XPRINTER env var.
 * If XPRINTER env var is not present looks for PDPRINTER, LPDEST, and
 * PRINTER (in that order)
 * See CDE's DtPrintSetupBox(3) manual page, too...
 */
static
const char *XpuGetDefaultXpPrintername(void)
{
  const char *s;
  /* BUG/TODO: XpPrinter resource needs to be sourced, too... */
  s = getenv("XPRINTER");
  if( !s )
  {
    s = getenv("PDPRINTER");
    if( !s )
    {
      s = getenv("LPDEST");
      if( !s )
      {
        s = getenv("PRINTER");
      }
    }
  }  
  return s;
}

static
const char *XpuGetXpServerList( void )
{
  const char *s;
  /* BUG/TODO: XpServerList resource needs to be sourced first, then append 
   * contents of XPSERVERLIST, then remove duplicates...
   */
  s = getenv("XPSERVERLIST"); 
  if( s == NULL )
    s = "";
    
  return(s);
}


Bool XpuXprintServersAvailable( void )
{
  const char *s;
  int         c = 0;
  /* BUGs/ToDo:
   * - XpServerList resource needs to be sourced, too...
   *   (see comment for |XpuGetXpServerList|, too)
   * - There should be some validation whether the server entries
   *   are
   *     a) valid (easy :)
   *   and
   *     b) available (hard to implement when XOpenDisplay() should be avoided)
   */
  s = getenv("XPSERVERLIST");
  /* Check if serverlist is non-empty */
  if (s)
  {
    while( *s++ )
    {
      if( !isspace(*s) )
        c++;
    }
  }
  /* a valid server name must at least contain the ':'-seperator
   * and a number (e.g. ":1") */
  return( c >= 2 );
}


static 
int XpuGetPrinter2( char *printer, char *display, Display **pdpyptr, XPContext *pcontextptr )
{
  Display   *pdpy;
  XPContext  pcontext;
  
  XPU_DEBUG_ONLY(printf("XpuGetPrinter2: probing display '%s' for '%s'\n", XPU_NULLXSTR(display), XPU_NULLXSTR(printer)));
  
  if( (pdpy = XOpenDisplay(display)) != NULL )
  {
    if( XpuCheckExtension(pdpy) )
    {
      XPPrinterList list;
      int           list_count;
      
      /* get list of available printers... */
      list = XpGetPrinterList(pdpy, printer, &list_count);        
      if( list != NULL ) XpFreePrinterList(list);
      
      /* ...and check if printer exists... */
      if( (list != NULL) && (list_count > 0) )
      {
        if( (pcontext = XpCreateContext(pdpy, printer)) != None )
        {
          *pdpyptr     = pdpy;
          *pcontextptr = pcontext;
          return(1);
        }
        
        XPU_DEBUG_ONLY(printf("XpuGetPrinter2: could not create print context for '%s'\n", XPU_NULLXSTR(printer)));
      }
    }
    else
    {
      XPU_DEBUG_ONLY(printf("display '%s' does not support the Xprint extension\n", XPU_NULLXSTR(display)));
    }  

    XCloseDisplay(pdpy);
    return(0);
  }
  else
  {
    XPU_DEBUG_ONLY(printf("could not open display '%s'\n", XPU_NULLXSTR(display)));
    return(0);
  }
}


/* acceps "printer" or "printer@display" */
int XpuGetPrinter( const char *arg_printername, Display **pdpyptr, XPContext *pcontextptr )
{
  Display       *pdpy;
  XPContext      pcontext;
  char          *printername;
  char          *s;
  char          *tok_lasts;
  
  *pdpyptr     = NULL;
  *pcontextptr = None;
  
  XPU_DEBUG_ONLY(printf("XpuGetPrinter: looking for '%s'\n", XPU_NULLXSTR(arg_printername)));
  
  /* strtok_r will modify string - duplicate it first... */
  printername = strdup(arg_printername);
  if( printername == NULL )
    return(0);
  
  if( (s = strtok_r(printername, "@", &tok_lasts)) != NULL )
  {
    char *name = s;
    char *display = strtok_r(NULL, "@", &tok_lasts);
    
    /* if we have a display - open it and grab printer */
    if( display != NULL )
    {
      if( XpuGetPrinter2(name, display, pdpyptr, pcontextptr) )
      {
        free(printername);
        return(1);
      }
    }
    /* if we did not get a display, travel througth all displays */
    else
    {
      char *sl = strdup(XpuGetXpServerList());
      
      if( sl != NULL )
      {
        for( display = strtok_r(sl, XPServerListSeparators, &tok_lasts) ; 
             display != NULL ; 
             display = strtok_r(NULL, XPServerListSeparators, &tok_lasts) )
        {       
          if( XpuGetPrinter2(name, display, pdpyptr, pcontextptr) )
          {
            free(sl);
            free(printername);
            return(1);
          } 
        }
        
        free(sl);
      }
    }
  }
  
  free(printername);
  XPU_DEBUG_ONLY(printf("XpuGetPrinter: failure\n"));
  
  return(0);
}


void XpuClosePrinterDisplay(Display *pdpy, XPContext pcontext)
{
  if( pdpy )
  {
    if( pcontext != None )
      XpDestroyContext(pdpy, pcontext);
      
    XCloseDisplay(pdpy);
  }
}

void XpuSetOneAttribute( Display *pdpy, XPContext pcontext, 
                         XPAttributes type, const char *attribute_name, const char *value, XPAttrReplacement replacement_rule )
{
  /* Alloc buffer for sprintf() stuff below */
  char *buffer = (char *)malloc(strlen(attribute_name)+strlen(value)+4);
  
  if( buffer != NULL )
  {
    sprintf(buffer, "%s: %s", attribute_name, value);      
    XpSetAttributes(pdpy, pcontext, type, buffer, replacement_rule);
    free(buffer);
  }  
}

void XpuSetOneLongAttribute( Display *pdpy, XPContext pcontext, 
                             XPAttributes type, const char *attribute_name, long value, XPAttrReplacement replacement_rule )
{
  /* Alloc buffer for sprintf() stuff below */
  char *buffer = (char *)malloc(strlen(attribute_name)+32+4);
  
  if( buffer != NULL )
  {
    sprintf(buffer, "%s: %ld", attribute_name, value);      
    XpSetAttributes(pdpy, pcontext, type, buffer, replacement_rule);
    free(buffer);
  }  
}

/* Check if attribute value is supported or not
 * Use this function _only_ if XpuGetSupported{Job,Doc,Page}Attributes()
 * does not help you...
 */
int XpuCheckSupported( Display *pdpy, XPContext pcontext, XPAttributes type, const char *attribute_name, const char *query )
{
  char *value;
  void *tok_lasts;
  
  MAKE_STRING_WRITABLE(attribute_name);
  if( attribute_name == NULL )
    return(0);
    
  value = XpGetOneAttribute(pdpy, pcontext, type, STRING_AS_WRITABLE(attribute_name));   
  
  XPU_DEBUG_ONLY(printf("XpuCheckSupported: XpGetOneAttribute(%s) returned '%s'\n", XPU_NULLXSTR(attribute_name), XPU_NULLXSTR(value)));

  FREE_WRITABLE_STRING(attribute_name);
  
  if( value != NULL )
  {
    const char *s;
    
    for( s = XpuEnumerateXpAttributeValue(value, &tok_lasts) ; s != NULL ; s = XpuEnumerateXpAttributeValue(NULL, &tok_lasts) )
    {
      XPU_DEBUG_ONLY(printf("XpuCheckSupported: probing '%s'=='%s'\n", XPU_NULLXSTR(s), XPU_NULLXSTR(query)));
      if( !strcmp(s, query) )
      {
        XFree(value);
        XpuDisposeEnumerateXpAttributeValue(&tok_lasts);
        return(1);
      }  
    }
    
    XpuDisposeEnumerateXpAttributeValue(&tok_lasts);
    XFree(value);
  }  
  
  return(0);
}


int XpuSetJobTitle( Display *pdpy, XPContext pcontext, const char *title )
{
  if( XpuGetSupportedJobAttributes(pdpy, pcontext) & XPUATTRIBUTESUPPORTED_JOB_NAME )
  {
    char *encoded_title;
    
    encoded_title = XpuResourceEncode(title);
    if (!encoded_title)
      return(0);
    XpuSetOneAttribute(pdpy, pcontext, XPJobAttr, "*job-name", encoded_title, XPAttrMerge);
    XpuResourceFreeString(encoded_title);
    return(1);
  }
  else
  {
    XPU_DEBUG_ONLY(printf("XpuSetJobTitle: XPUATTRIBUTESUPPORTED_JOB_NAME not supported ('%s')\n", XPU_NULLXSTR(title)));
    return(0); 
  }  
}
    
int XpuGetOneLongAttribute( Display *pdpy, XPContext pcontext, XPAttributes type, const char *attribute_name, long *result )
{
  char *s;
  
  MAKE_STRING_WRITABLE(attribute_name);
  if( attribute_name == NULL )
    return(0);
  s = XpGetOneAttribute(pdpy, pcontext, type, STRING_AS_WRITABLE(attribute_name));
  
  if(s && *s) 
  {
    long tmp;
    
    XPU_DEBUG_ONLY(printf("XpuGetOneLongAttribute: '%s' got '%s'\n", XPU_NULLXSTR(attribute_name), XPU_NULLXSTR(s)));
    
    tmp = strtol(s, (char **)NULL, 10);
    
    if( !(((tmp == 0L) || (tmp == LONG_MIN) || (tmp == LONG_MAX)) && 
          ((errno == ERANGE) || (errno == EINVAL))) )
    {
      *result = tmp;
      XFree(s);
      XPU_DEBUG_ONLY(printf("XpuGetOneLongAttribute: result %ld\n", *result));
      FREE_WRITABLE_STRING(attribute_name);
      return(1);
    }            
  }
  
  if( s != NULL ) 
    XFree(s);
  
  FREE_WRITABLE_STRING(attribute_name);
  
  return(0);
}


#ifdef DEBUG
/* debug only */
void dumpXpAttributes( Display *pdpy, XPContext pcontext )
{
  char *s;
  printf("------------------------------------------------\n");
  printf("--> Job\n%s\n",         s=XpuGetJobAttributes(pdpy, pcontext));     XFree(s);
  printf("--> Doc\n%s\n",         s=XpuGetDocAttributes(pdpy, pcontext));     XFree(s);
  printf("--> Page\n%s\n",        s=XpuGetPageAttributes(pdpy, pcontext));    XFree(s);
  printf("--> Printer\n%s\n",     s=XpuGetPrinterAttributes(pdpy, pcontext)); XFree(s);
  printf("--> Server\n%s\n",      s=XpuGetServerAttributes(pdpy, pcontext));  XFree(s);
  printf("image resolution %d\n", (int)XpGetImageResolution(pdpy, pcontext));
  printf("------------------------------------------------\n");
}
#endif /* DEBUG */    


typedef struct XpuIsNotifyEventContext_
{
  int event_base;
  int detail;
} XpuIsNotifyEventContext;

static
Bool IsXpNotifyEvent( Display *pdpy, XEvent *ev, XPointer arg )
{
  Bool match;
  XpuIsNotifyEventContext *context = (XpuIsNotifyEventContext *)arg;
  XPPrintEvent *pev = (XPPrintEvent *)ev;
  
  match = pev->type == (context->event_base+XPPrintNotify) && 
          pev->detail == context->detail;

  XPU_DEBUG_ONLY(printf("XpuWaitForPrintNotify: %d=IsXpNotifyEvent(%d,%d)\n",
                 (int)match,
                 (int)pev->type,
                 (int)pev->detail));
  return match;
}

void XpuWaitForPrintNotify( Display *pdpy, int xp_event_base, int detail )
{
  XEvent                  dummy;
  XpuIsNotifyEventContext matchcontext;

  matchcontext.event_base = xp_event_base;
  matchcontext.detail     = detail;
  XIfEvent(pdpy, &dummy, IsXpNotifyEvent, (XPointer)&matchcontext);
}      

static
const char *skip_matching_brackets(const char *start)
{
  const char *s     = start;
  int         level = 0;
  
  if( !start )
      return(NULL);
  
  do
  {
    switch(*s++)
    {
      case '\0': return(NULL);
      case '{': level++; break;
      case '}': level--; break;
    }
  } while(level > 0);

  return(s);
}


static
const char *search_next_space(const char *start)
{
  const char *s     = start;
  int         level = 0;
  
  if( !start )
    return(NULL);
  
  for(;;)
  {   
    if( isspace(*s) )
      return(s);

    if( *s=='\0' )
      return(NULL);      

    s++;
  }
}

/* PRIVATE context data for XpuEnumerateXpAttributeValue() */
typedef struct _XpuAttributeValueEnumeration
{
  char   *value;
  size_t  original_value_len; /* original length of value */
  char   *group;
  char   *start;
  char   *s;
} XpuAttributeValueEnumeration;


/* Hacked parser for Xp values and enumerations */
static
const char *XpuEnumerateXpAttributeValue( const char *value, void **vcptr )
{
  XpuAttributeValueEnumeration **cptr = (XpuAttributeValueEnumeration **)vcptr;
  XpuAttributeValueEnumeration  *context;
  const char                    *tmp;
  
  if( !cptr )
    return(NULL);
    
  if( value )
  {
    XpuAttributeValueEnumeration *e;
    const char *s = value;
    Bool        isGroup = FALSE;
  
    e = (XpuAttributeValueEnumeration *)malloc(sizeof(XpuAttributeValueEnumeration));
    if( !e )
      return NULL;
  
    /* Skip leading '{'. */
    while(*s=='{' && isGroup==FALSE)
    {
      s++;
      isGroup = TRUE;
    }  
    /* Skip leading blanks. */
    while(isspace(*s))
      s++;
    
    e->group = NULL;
    
    /* Read group name. */
    if( isGroup )
    { 
      tmp = s;  
      while(!isspace(*s))
        s++;
      if(strncmp(tmp, "''", s-tmp) != 0)
      {
        e->group = strdup(tmp);
        e->group[s-tmp] = '\0';
      }
    }
  
    e->original_value_len = strlen(s);
    e->value = (char *)malloc(e->original_value_len+4); /* We may look up to three bytes beyond the string */
    strcpy(e->value, s);
    memset(e->value+e->original_value_len+1, 0, 3); /* quad termination */
    e->start = e->s = e->value;
    
    *cptr = e;
  }
  
  context = *cptr;
  
  if( !context || !context->s )
    return(NULL);
   
  /* Skip leading blanks, '\'' or '}' */
  while(isspace(*(context->s)) || *(context->s)=='\'' /*|| *(context->s)=='}'*/ )
    context->s++;

  if( *(context->s) == '\0' )
    return(NULL);

  context->start = context->s;
  if( *(context->start) == '{' )
    context->s = (char *)skip_matching_brackets(context->start);
  else
    context->s = (char *)search_next_space(context->start);
    
  /* end of string reached ? */
  if( context->s )
  {   
    *(context->s) = '\0';
    context->s++;
  }
  
  /* Check if we reached a new attribute group */
  tmp = context->start;
  while(isspace(*tmp))
    tmp++;   
  if( *tmp=='}' )
  {
    void *prev_cptr = *vcptr;
    
    tmp+=2; /* We have 3*'\0' at the end of the string - this is legal! */
    if( *tmp!='\0' )
    {
      const char *ret;
   
      /* Start the parser again */
      *vcptr = NULL;
      ret = XpuEnumerateXpAttributeValue(tmp, vcptr);
    
      /* Free old context */
      XpuDisposeEnumerateXpAttributeValue(&prev_cptr);
    
      return(ret);
    }
    else
    {
      return(NULL);
    }
  }
  
  return(context->start);   
}

/* Get enumeration group for last string returned by |XpuEnumerateXpAttributeValue|... */
static
const char *XpuGetCurrentAttributeGroup( void **vcptr )
{
  XpuAttributeValueEnumeration **cptr = (XpuAttributeValueEnumeration **)vcptr;
  if( !cptr )
    return(NULL);
  if( !*cptr )
    return(NULL);
    
  return((*cptr)->group);
}


static
void XpuDisposeEnumerateXpAttributeValue( void **vc )
{ 
  if( vc )
  {
    XpuAttributeValueEnumeration *context = *((XpuAttributeValueEnumeration **)vc);
    free(context->value);
    if(context->group)
      free(context->group);
    free(context);   
  }
}

/* parse a paper size string 
 * (example: '{na-letter False {6.3500 209.5500 6.3500 273.0500}}') */
static
Bool XpuParseMediumSourceSize( const char *value, 
                               const char **medium_name, int *mbool, 
                               float *ma1, float *ma2, float *ma3, float *ma4 )
{
  const char *s;
  char       *d, 
             *name;
  char       *boolbuf;
  size_t      value_len;
  int         num_input_items;
  const char *cur_locale;
  
  if( value && value[0]!='{' && value[0]!='\0' )
    return(False);
    
  value_len = strlen(value);
  
  /* alloc buffer for |medium_name| and |boolbuf| in one step
   * (both must be large enougth to hold at least |strlen(value)+1| bytes) */  
  name = (char *)malloc(value_len*2 + 4);
  boolbuf = name + value_len+2; /* |boolbuf| starts directly after |name| */
  
  /* remove '{' && '}' */
  s = value;
  d = name;
  do
  {
    *d = tolower(*s);
    
    if( *s!='{' && *s!='}' )
      d++;
    
    s++;  
  }
  while(*s);
  *d = '\0';
    
  /* separate medium name from string */
  d = (char *)search_next_space(name);
  if( !d )
  {
    free(name);
    return(False);
  }  
  *d = '\0';
  *medium_name = name;
  
  /* ... continue to parse the remaining string... */
  d++;
  

  /* Force C/POSIX radix for scanf()-parsing (see bug 131831 ("Printing
   * does not work in de_AT@euro locale")), do the parsing and restore
   * the original locale.
   * XXX: This may affect all threads and not only the calling one...
   */
  {
#define CUR_LOCALE_SIZE 256
    char cur_locale[CUR_LOCALE_SIZE+1];
    strncpy(cur_locale, setlocale(LC_NUMERIC, NULL), CUR_LOCALE_SIZE);
    cur_locale[CUR_LOCALE_SIZE]='\0';
    setlocale(LC_NUMERIC, "C"); 
    num_input_items = sscanf(d, "%s %f %f %f %f", boolbuf, ma1, ma2, ma3, ma4);
    setlocale(LC_NUMERIC, cur_locale);
#undef CUR_LOCALE_SIZE
  }

  if( num_input_items != 5 )
  {
    free(name);
    return(False);
  }

  if( !strcmp(boolbuf, "true") )
    *mbool = True;
  else if( !strcmp(boolbuf, "false") )
    *mbool = False;
  else
  {
    free(name);
    return(False);    
  }
  return(True);
}


/* parse a paper size string 
 * (example: '{na-letter False {6.3500 209.5500 6.3500 273.0500}}') */
static
Bool XpuEnumerateMediumSourceSizes( Display *pdpy, XPContext pcontext,
                                    const char **tray_name,
                                    const char **medium_name, int *mbool, 
                                    float *ma1, float *ma2, float *ma3, float *ma4,
                                    void **vcptr )
{
  const char *medium_spec;
  const char *value = NULL;
  
  if( pdpy && pcontext )
  {
    value = XpGetOneAttribute(pdpy, pcontext, XPPrinterAttr, "medium-source-sizes-supported");
    if( !value )
      return(False);
  }

  while(1)
  {  
    medium_spec = XpuEnumerateXpAttributeValue(value, vcptr);
    
    if( value )
    {
      XFree((void *)value);
      value = NULL;
    }

    /* enumeration done? */
    if( !medium_spec )
      return(False);

    if (XpuParseMediumSourceSize(medium_spec, 
                                 medium_name, mbool, 
                                 ma1, ma2, ma3, ma4))
    {
      *tray_name = XpuGetCurrentAttributeGroup(vcptr);
      return(True);
    }
    else
    {
      /* Should never ever happen! */
      fprintf(stderr, "XpuEnumerateMediumSourceSize: error parsing '%s'\n", medium_spec);
    }
  }
  /* not reached */   
}

static
void XpuDisposeEnumerateMediumSourceSizes( void **vc )
{
  XpuDisposeEnumerateXpAttributeValue(vc);
}  


/* future: Migrate this functionality into |XpGetPrinterList| - just do
 * not pass a |Display *| to |XpGetPrinterList|
 */
XPPrinterList XpuGetPrinterList( const char *printer, int *res_list_count )
{
  XPPrinterRec *rec = NULL;
  int           rec_count = 1; /* Allocate one more |XPPrinterRec| structure
                                * as terminator */
  char         *sl;
  const char   *default_printer_name = XpuGetDefaultXpPrintername();
  int           default_printer_rec_index = -1;

  if( !res_list_count )
    return(NULL); 
  
  sl = strdup(XpuGetXpServerList());
  MAKE_STRING_WRITABLE(printer);
    
  if( sl != NULL )
  {
    char *display;
    char *tok_lasts;
    
    for( display = strtok_r(sl, XPServerListSeparators, &tok_lasts) ; 
         display != NULL ; 
         display = strtok_r(NULL, XPServerListSeparators, &tok_lasts) )
    {
      Display *pdpy;
      
      if( (pdpy = XOpenDisplay(display)) != NULL )
      {
        XPPrinterList list;
        int           list_count;
        size_t        display_len = strlen(display);

        /* get list of available printers... */
        list = XpGetPrinterList(pdpy, STRING_AS_WRITABLE(printer), &list_count);        
      
        if( list && list_count )
        {
          int i;
          
          for( i = 0 ; i < list_count ; i++ )
          {
            char *s;
            
            /* Workaround for http://bugzilla.mozilla.org/show_bug.cgi?id=193499 
             * ("Xprint print/print preview crashes Mozilla") where the Solaris
             * Xprt may create invalid entries (e.g. |XpGetPrinterList| will
             * return |list[i].name==NULL| due to empty lines in the printer list.
             */
            if( !list[i].name )
              continue;
            
            rec_count++;
            rec = (XPPrinterRec *)realloc(rec, sizeof(XPPrinterRec)*rec_count);
            if( !rec ) /* failure */
              break;
              
            s = (char *)malloc(strlen(list[i].name)+display_len+4);
            sprintf(s, "%s@%s", list[i].name, display);
            rec[rec_count-2].name = s;
            rec[rec_count-2].desc = (list[i].desc)?(strdup(list[i].desc)):(NULL);
            
            /* Test for default printer (if the user set one).*/
            if( default_printer_name )
            {
              /* Default_printer_name may either contain the FQPN(=full
               * qualified printer name ("foo@myhost:5") or just the name
               * ("foo")) */
              if( (!strcmp(list[i].name, default_printer_name)) ||
                  (!strcmp(s,            default_printer_name)) )
              {
                /* Remember index of default printer that we can swap it to 
                 * the head of the array below... */
                default_printer_rec_index = rec_count-2;
              }
            }  
          }
          
          XpFreePrinterList(list);
        }
                 
        XCloseDisplay(pdpy);
      }
    }
    
    free(sl);
  }
  
  if( rec )
  {
    /* users: DO NOT COUNT ON THIS DETAIL 
     * (this is only to make current impl. of XpuFreePrinterList() easier)
     * I may remove this implementation detail in a later revision of
     * the library!
     */
    rec[rec_count-1].name = NULL;
    rec[rec_count-1].desc = NULL;
    rec_count--;
  }
  else
  {
    rec_count = 0;
  }
  
  /* The default printer is always the first one in the printer list... */
  if( (default_printer_rec_index != -1) && rec )
  {
    XPPrinterRec tmp;
    tmp = rec[0];
    rec[0] = rec[default_printer_rec_index];
    rec[default_printer_rec_index] = tmp;
  }
    
  *res_list_count = rec_count;
  FREE_WRITABLE_STRING(printer);
  return(rec);
}      


void XpuFreePrinterList( XPPrinterList list )
{
  if( list )
  {
    XPPrinterRec *curr = list;
  
    /* See the warning abouve about using this implementation detail for
     * checking for the list's end... */
    while( curr->name != NULL )
    {
      free(curr->name);
      if(curr->desc)
        free(curr->desc);
      curr++;
    }
  
    free(list);
  }
}

/* Set number of copies to print from this document */
int XpuSetDocumentCopies( Display *pdpy, XPContext pcontext, long num_copies )
{
  if( XpuGetSupportedDocAttributes(pdpy, pcontext) & XPUATTRIBUTESUPPORTED_COPY_COUNT)
  {
    XpuSetOneLongAttribute(pdpy, pcontext, XPDocAttr, "*copy-count", num_copies, XPAttrMerge);
    return(1);
  }
  else
  {
    XPU_DEBUG_ONLY(printf("XpuSetContentOrientation: XPUATTRIBUTESUPPORTED_COPY_COUNT not supported\n"));
       
    /* Failure... */
    return(0);
  }  
}

XpuMediumSourceSizeList XpuGetMediumSourceSizeList( Display *pdpy, XPContext pcontext, int *numEntriesPtr )
{
  XpuMediumSourceSizeList list = NULL;
  int                     rec_count = 1; /* allocate one more |XpuMediumSourceSizeRec| structure
                                          * as terminator */
  Bool                    status;
  float                   ma1,
                          ma2,
                          ma3,
                          ma4;
  char                   *value;
  void                   *tok_lasts;
  const char             *tray_name,
                         *medium_name;
  int                     mbool;
  const char             *default_tray,
                         *default_medium;
  int                     default_medium_rec_index = -1;
  
  default_tray   = XpGetOneAttribute(pdpy, pcontext, XPDocAttr, "default-input-tray");
  if(!default_tray)
  {
    fprintf(stderr, "XpuGetMediumSourceSizeList: Internal error, no 'default-input-tray' found.\n");
    return(NULL);
  }
  default_medium = XpGetOneAttribute(pdpy, pcontext, XPDocAttr, "default-medium");
  if(!default_medium)
  {
    fprintf(stderr, "XpuGetMediumSourceSizeList: Internal error, no 'default-medium' found.\n");
    XFree((void *)default_tray);
    return(NULL);
  }
  
  for( status = XpuEnumerateMediumSourceSizes(pdpy, pcontext, &tray_name, &medium_name, &mbool,
                                              &ma1, &ma2, &ma3, &ma4, &tok_lasts) ;
       status != False ;
       status = XpuEnumerateMediumSourceSizes(NULL, None,     &tray_name, &medium_name, &mbool, 
                                              &ma1, &ma2, &ma3, &ma4, &tok_lasts) )
  {
    rec_count++;
    list = (XpuMediumSourceSizeRec *)realloc(list, sizeof(XpuMediumSourceSizeRec)*rec_count);
    if( !list )
      return(NULL);
    
    list[rec_count-2].tray_name   = (tray_name)?(strdup(tray_name)):(NULL);
    list[rec_count-2].medium_name = strdup(medium_name);
    list[rec_count-2].mbool       = mbool;
    list[rec_count-2].ma1         = ma1;
    list[rec_count-2].ma2         = ma2;
    list[rec_count-2].ma3         = ma3;
    list[rec_count-2].ma4         = ma4;
    
    /* Default medium ? */
    if( (!strcmp(medium_name, default_medium)) && 
        ((tray_name && (*default_tray))?(!strcmp(tray_name, default_tray)):(True)) )
    {
      default_medium_rec_index = rec_count-2;
    }
  }  

  XpuDisposeEnumerateMediumSourceSizes(&tok_lasts);

  if( list )
  {
    /* users: DO NOT COUNT ON THIS DETAIL 
     * (this is only to make current impl. of XpuFreeMediumSourceSizeList() easier)
     * I may remove this implementation detail in a later revision of
     * the library! */
    list[rec_count-1].tray_name  = NULL;
    list[rec_count-1].medium_name = NULL;
    rec_count--;
  }
  else
  {
    rec_count = 0;
  }

  /* Make the default medium always the first item in the list... */
  if( (default_medium_rec_index != -1) && list )
  {
    XpuMediumSourceSizeRec tmp;
    tmp = list[0];
    list[0] = list[default_medium_rec_index];
    list[default_medium_rec_index] = tmp;
  }

  *numEntriesPtr = rec_count; 
  return(list);
}

void XpuFreeMediumSourceSizeList( XpuMediumSourceSizeList list )
{
  if( list )
  {
    XpuMediumSourceSizeRec *curr = list;
  
    /* See the warning abouve about using this implementation detail for
     * checking for the list's end... */
    while( curr->medium_name != NULL )
    {
      if( curr->tray_name)
        free((void *)curr->tray_name);
      free((void *)curr->medium_name);
      curr++;
    }
  
    free(list);
  }
}

static
int XpuSetMediumSourceSize( Display *pdpy, XPContext pcontext, XPAttributes type, XpuMediumSourceSizeRec *medium_spec )
{
  /* Set the "default-medium" and "*default-input-tray" 
   * (if |XpuEnumerateMediumSourceSizes| returned one) XPDocAttr's
   * attribute and return */
  if (medium_spec->tray_name)
  {
    XpuSetOneAttribute(pdpy, pcontext, type, "*default-input-tray", medium_spec->tray_name, XPAttrMerge);
  }
  XpuSetOneAttribute(pdpy, pcontext, type, "*default-medium", medium_spec->medium_name, XPAttrMerge);
  
  return( 1 );
}

/* Set document medium size */
int XpuSetDocMediumSourceSize( Display *pdpy, XPContext pcontext, XpuMediumSourceSizeRec *medium_spec )
{
  XpuSupportedFlags doc_supported_flags;
  
  doc_supported_flags = XpuGetSupportedDocAttributes(pdpy, pcontext);

  if( (doc_supported_flags & XPUATTRIBUTESUPPORTED_DEFAULT_MEDIUM) == 0 )
    return( 0 );
    
  if (medium_spec->tray_name)
  {
    if( (doc_supported_flags & XPUATTRIBUTESUPPORTED_DEFAULT_INPUT_TRAY) == 0 )
      return( 0 );  
  }

  return XpuSetMediumSourceSize(pdpy, pcontext, XPDocAttr, medium_spec);
}

/* Set page medium size */
int XpuSetPageMediumSourceSize( Display *pdpy, XPContext pcontext, XpuMediumSourceSizeRec *medium_spec )
{
  XpuSupportedFlags page_supported_flags;
  
  page_supported_flags = XpuGetSupportedPageAttributes(pdpy, pcontext);

  if( (page_supported_flags & XPUATTRIBUTESUPPORTED_DEFAULT_MEDIUM) == 0 )
    return( 0 );
    
  if (medium_spec->tray_name)
  {
    if( (page_supported_flags & XPUATTRIBUTESUPPORTED_DEFAULT_INPUT_TRAY) == 0 )
      return( 0 );  
  }

  return XpuSetMediumSourceSize(pdpy, pcontext, XPPageAttr, medium_spec);
}

#ifndef ABS
#define ABS(x) ((x)<0?-(x):(x))
#endif /* ABS */
#define MORE_OR_LESS_EQUAL(a, b, tolerance) (ABS((a) - (b)) <= (tolerance))

XpuMediumSourceSizeRec *
XpuFindMediumSourceSizeBySize( XpuMediumSourceSizeList mlist, int mlist_count, 
                               float page_width_mm, float page_height_mm, float tolerance )
{
  int i;
  for( i = 0 ; i < mlist_count ; i++ )
  {
    XpuMediumSourceSizeRec *curr = &mlist[i];
    float total_width  = curr->ma1 + curr->ma2,
          total_height = curr->ma3 + curr->ma4;

    /* Match width/height*/
    if( ((page_width_mm !=-1.f)?(MORE_OR_LESS_EQUAL(total_width,  page_width_mm,  tolerance)):(True)) &&
        ((page_height_mm!=-1.f)?(MORE_OR_LESS_EQUAL(total_height, page_height_mm, tolerance)):(True)) )
    {
      return(curr);
    }
  }

  return(NULL);
}

XpuMediumSourceSizeRec *
XpuFindMediumSourceSizeByBounds( XpuMediumSourceSizeList mlist, int mlist_count, 
                                 float m1, float m2, float m3, float m4, float tolerance )
{
  int i;
  for( i = 0 ; i < mlist_count ; i++ )
  {
    XpuMediumSourceSizeRec *curr = &mlist[i];

    /* Match bounds */
    if( ((m1!=-1.f)?(MORE_OR_LESS_EQUAL(curr->ma1, m1, tolerance)):(True)) &&
        ((m2!=-1.f)?(MORE_OR_LESS_EQUAL(curr->ma2, m2, tolerance)):(True)) &&
        ((m3!=-1.f)?(MORE_OR_LESS_EQUAL(curr->ma3, m3, tolerance)):(True)) &&
        ((m4!=-1.f)?(MORE_OR_LESS_EQUAL(curr->ma4, m4, tolerance)):(True)) )
    {
      return(curr);
    }
  }

  return(NULL);
}

XpuMediumSourceSizeRec *
XpuFindMediumSourceSizeByName( XpuMediumSourceSizeList mlist, int mlist_count, 
                               const char *tray_name, const char *medium_name )
{
  int i;
  for( i = 0 ; i < mlist_count ; i++ )
  {
    XpuMediumSourceSizeRec *curr = &mlist[i];

    /* Match by tray name and/or medium name */
    if( ((tray_name && curr->tray_name)?(!strcasecmp(curr->tray_name, tray_name)):(tray_name==NULL)) &&
        ((medium_name)?(!strcasecmp(curr->medium_name, medium_name)):(True)) )
    {
      return(curr);
    }
  }

  return(NULL);
}

XpuResolutionList XpuGetResolutionList( Display *pdpy, XPContext pcontext, int *numEntriesPtr )
{
  XpuResolutionList list = NULL;
  int               rec_count = 1; /* Allocate one more |XpuResolutionRec| structure
                                    * as terminator */
  char             *value;
  char             *tok_lasts;
  const char       *s;
  long              default_resolution = -1;
  int               default_resolution_rec_index = -1;
  char              namebuf[64];

  /* Get default document resolution */
  if( XpuGetOneLongAttribute(pdpy, pcontext, XPDocAttr, "default-printer-resolution", &default_resolution) != 1 )
  {
    default_resolution = -1;
  }
  
  value = XpGetOneAttribute(pdpy, pcontext, XPPrinterAttr, "printer-resolutions-supported");
  if (!value)
  {
    fprintf(stderr, "XpuGetResolutionList: Internal error, no 'printer-resolutions-supported' XPPrinterAttr found.\n");
    return(NULL);
  }
  
  for( s = strtok_r(value, " ", &tok_lasts) ;
       s != NULL ;
       s = strtok_r(NULL, " ", &tok_lasts) )
  {
    long tmp;
    
    tmp = strtol(s, (char **)NULL, 10);
    
    if( ((tmp == 0L) || (tmp == LONG_MIN) || (tmp == LONG_MAX)) && 
        ((errno == ERANGE) || (errno == EINVAL)) )
    {
      fprintf(stderr, "XpuGetResolutionList: Internal parser errror for '%s'.\n", s);
      continue;
    }    
  
    rec_count++;
    list = (XpuResolutionRec *)realloc(list, sizeof(XpuResolutionRec)*rec_count);
    if( !list )
      return(NULL);
    
    sprintf(namebuf, "%lddpi", tmp);
    list[rec_count-2].name   = strdup(namebuf);
    list[rec_count-2].x_dpi  = tmp;
    list[rec_count-2].y_dpi  = tmp;

    if( default_resolution != -1 )
    {
      /* Is this the default resolution ? */
      if( (list[rec_count-2].x_dpi == default_resolution) &&
          (list[rec_count-2].y_dpi == default_resolution) )
      {
        default_resolution_rec_index = rec_count-2;
      }
    }  
  }  

  XFree(value);

  if( list )
  {
    /* users: DO NOT COUNT ON THIS DETAIL 
     * (this is only to make current impl. of XpuGetResolutionList() easier)
     * We may remove this implementation detail in a later revision of
     * the library! */
    list[rec_count-1].name   = NULL;
    list[rec_count-1].x_dpi  = -1;
    list[rec_count-1].y_dpi  = -1;
    rec_count--;
  }
  else
  {
    rec_count = 0;
  }

  /* Make the default resolution always the first item in the list... */
  if( (default_resolution_rec_index != -1) && list )
  {
    XpuResolutionRec tmp;
    tmp = list[0];
    list[0] = list[default_resolution_rec_index];
    list[default_resolution_rec_index] = tmp;
  }

  *numEntriesPtr = rec_count; 
  return(list);
}

void XpuFreeResolutionList( XpuResolutionList list )
{
  if( list )
  { 
    XpuResolutionRec *curr = list;
  
    /* See the warning abouve about using this implementation detail for
     * checking for the list's end... */
    while( curr->name != NULL )
    {
      free((void *)curr->name);
      curr++;
    }  

    free(list);
  }
}

/* Find resolution in resolution list.
 */
XpuResolutionRec *XpuFindResolutionByName( XpuResolutionList list, int list_count, const char *name)
{
  int i;
  
  for( i = 0 ; i < list_count ; i++ )
  {
    XpuResolutionRec *curr = &list[i];
    if (!strcasecmp(curr->name, name))
      return curr;

    /* Search by plain DPI value (no "dpi" suffix )*/
    if (curr->x_dpi == curr->x_dpi)
    {
      char buf[32];
      sprintf(buf, "%ld", curr->x_dpi);
      if (!strcasecmp(buf, name))
        return curr;      
    }
  }

  return NULL;
}

/* Get default page (if defined) or document resolution
 * this function may fail in the following conditions:
 * - No default resolution set yet
 * - X DPI != Y DPI (not yet implemented in Xprt)
 */
Bool XpuGetResolution( Display *pdpy, XPContext pcontext, long *x_dpi_ptr, long *y_dpi_ptr )
{
  long dpi;

  /* Try to get the current page's resolution (pages may differ in resolution if the DDX supports this) */
  if( XpuGetOneLongAttribute(pdpy, pcontext, XPPageAttr, "default-printer-resolution", &dpi) == 1 )
  {
    *x_dpi_ptr = dpi;
    *y_dpi_ptr = dpi;
    return True;
  }

  /* Get document resolution */
  if( XpuGetOneLongAttribute(pdpy, pcontext, XPDocAttr, "default-printer-resolution", &dpi) == 1 )
  {
    *x_dpi_ptr = dpi;
    *y_dpi_ptr = dpi;
    return True;
  }

  return False;
}

static
int XpuSetResolution( Display *pdpy, XPContext pcontext, XPAttributes type, XpuResolutionRec *rec )
{
  if( rec->x_dpi != rec->y_dpi )
  {
    fprintf(stderr, "XpuSetResolution: internal error: x_dpi != y_dpi not supported yet.\n");
    return 0;
  }

  XpuSetOneLongAttribute(pdpy, pcontext, type, "*default-printer-resolution", rec->x_dpi, XPAttrMerge); 
  return( 1 );
}

/* Set document resolution 
 * Retun error if printer does not support setting a resolution
 */
int XpuSetDocResolution( Display *pdpy, XPContext pcontext, XpuResolutionRec *rec )
{
  if( (XpuGetSupportedDocAttributes(pdpy, pcontext) & XPUATTRIBUTESUPPORTED_DEFAULT_PRINTER_RESOLUTION) == 0 )
    return( 0 );
    
  return XpuSetResolution(pdpy, pcontext, XPDocAttr, rec);
}

/* Set page medium size 
 * Retun error if printer does not support setting a resolution or if per-page
 * resolution changes are not allowed.
 */
int XpuSetPageResolution( Display *pdpy, XPContext pcontext, XpuResolutionRec *rec )
{
  if( (XpuGetSupportedPageAttributes(pdpy, pcontext) & XPUATTRIBUTESUPPORTED_DEFAULT_PRINTER_RESOLUTION) == 0 )
    return( 0 );
    
  return XpuSetResolution(pdpy, pcontext, XPPageAttr, rec);
}

XpuOrientationList XpuGetOrientationList( Display *pdpy, XPContext pcontext, int *numEntriesPtr )
{
  XpuOrientationList list = NULL;
  int                rec_count = 1; /* Allocate one more |XpuOrientationRec|
                                     * structure as terminator */
  char              *value;
  char              *tok_lasts;
  const char        *s;
  const char        *default_orientation = NULL;
  int                default_orientation_rec_index = -1;

  /* Get default document orientation */
  default_orientation = XpGetOneAttribute(pdpy, pcontext, XPDocAttr, "content-orientation"); 
  if( !default_orientation )
  {
    fprintf(stderr, "XpuGetOrientationList: Internal error, no 'content-orientation' XPDocAttr found.\n");
    return(NULL);
  }
  
  value = XpGetOneAttribute(pdpy, pcontext, XPPrinterAttr, "content-orientations-supported");
  if (!value)
  {
    fprintf(stderr, "XpuGetOrientationList: Internal error, no 'content-orientations-supported' XPPrinterAttr found.\n");
    return(NULL);
  }
  
  for( s = strtok_r(value, " ", &tok_lasts) ;
       s != NULL ;
       s = strtok_r(NULL, " ", &tok_lasts) )
  { 
    rec_count++;
    list = (XpuOrientationRec *)realloc(list, sizeof(XpuOrientationRec)*rec_count);
    if( !list )
      return(NULL);
    
    list[rec_count-2].orientation = strdup(s);

    /* Default resolution ? */
    if( !strcmp(list[rec_count-2].orientation, default_orientation) )
    {
      default_orientation_rec_index = rec_count-2;
    }
  }  

  XFree(value);
  XFree((void *)default_orientation);

  if( list )
  {
    /* users: DO NOT COUNT ON THIS DETAIL 
     * (this is only to make current impl. of XpuFreeOrientationList() easier)
     * I may remove this implementation detail in a later revision of
     * the library! */
    list[rec_count-1].orientation = NULL;
    rec_count--;
  }
  else
  {
    rec_count = 0;
  }

  /* Make the default orientation always the first item in the list... */
  if( (default_orientation_rec_index != -1) && list )
  {
    XpuOrientationRec tmp;
    tmp = list[0];
    list[0] = list[default_orientation_rec_index];
    list[default_orientation_rec_index] = tmp;
  }

  *numEntriesPtr = rec_count; 
  return(list);
}

void XpuFreeOrientationList( XpuOrientationList list )
{
  if( list )
  {
    XpuOrientationRec *curr = list;
  
    /* See the warning abouve about using this implementation detail for
     * checking for the list's end... */
    while( curr->orientation != NULL )
    {
      free((void *)curr->orientation);
      curr++;
    }   
    free(list);
  }
}

XpuOrientationRec *
XpuFindOrientationByName( XpuOrientationList list, int list_count, const char *orientation )
{
  int i;
  
  for( i = 0 ; i < list_count ; i++ )
  {
    XpuOrientationRec *curr = &list[i];
    if (!strcasecmp(curr->orientation, orientation))
      return curr;
  }

  return(NULL);
}

static
int XpuSetOrientation( Display *pdpy, XPContext pcontext, XPAttributes type, XpuOrientationRec *rec )
{
  XpuSetOneAttribute(pdpy, pcontext, type, "*content-orientation", rec->orientation, XPAttrMerge);
  return(1);
}

/* Set document orientation 
 * Retun error if printer does not support setting an orientation
 */
int XpuSetDocOrientation( Display *pdpy, XPContext pcontext, XpuOrientationRec *rec )
{
  if( (XpuGetSupportedDocAttributes(pdpy, pcontext) & XPUATTRIBUTESUPPORTED_CONTENT_ORIENTATION) == 0 )
    return( 0 );
    
  return XpuSetOrientation(pdpy, pcontext, XPDocAttr, rec);
}

/* Set page orientation
 * Retun error if printer does not support setting an orientation or if
 * per-page orientations changes are not allowed
 */
int XpuSetPageOrientation( Display *pdpy, XPContext pcontext, XpuOrientationRec *rec )
{
  if( (XpuGetSupportedPageAttributes(pdpy, pcontext) & XPUATTRIBUTESUPPORTED_CONTENT_ORIENTATION) == 0 )
    return( 0 );
    
  return XpuSetOrientation(pdpy, pcontext, XPPageAttr, rec);
}

XpuPlexList XpuGetPlexList( Display *pdpy, XPContext pcontext, int *numEntriesPtr )
{
  XpuPlexList  list = NULL;
  int          rec_count = 1; /* Allocate one more |XpuPlexList| structure
                               * as terminator */
  char        *value;
  char        *tok_lasts;
  const char  *s;
  const char  *default_plex = NULL;
  int          default_plex_rec_index = -1;

  /* Get default document plex */
  default_plex = XpGetOneAttribute(pdpy, pcontext, XPDocAttr, "plex"); 
  if( !default_plex )
  {
    fprintf(stderr, "XpuGetPlexList: Internal error, no 'plex' XPDocAttr found.\n");
    return(NULL);
  }
   
  value = XpGetOneAttribute(pdpy, pcontext, XPPrinterAttr, "plexes-supported");
  if (!value)
  {
    fprintf(stderr, "XpuGetPlexList: Internal error, no 'plexes-supported' XPPrinterAttr found.\n");
    return(NULL);
  }
  
  for( s = strtok_r(value, " ", &tok_lasts) ;
       s != NULL ;
       s = strtok_r(NULL, " ", &tok_lasts) )
  { 
    rec_count++;
    list = (XpuPlexRec *)realloc(list, sizeof(XpuPlexRec)*rec_count);
    if( !list )
      return(NULL);
    
    list[rec_count-2].plex = strdup(s);

    /* Default plex ? */
    if( !strcmp(list[rec_count-2].plex, default_plex) )
    {
      default_plex_rec_index = rec_count-2;
    }
  }  

  XFree(value);
  XFree((void *)default_plex);

  if( list )
  {
    /* users: DO NOT COUNT ON THIS DETAIL 
     * (this is only to make current impl. of XpuFreePlexList() easier)
     * I may remove this implementation detail in a later revision of
     * the library! */
    list[rec_count-1].plex = NULL;
    rec_count--;
  }
  else
  {
    rec_count = 0;
  }

  /* Make the default plex always the first item in the list... */
  if( (default_plex_rec_index != -1) && list )
  {
    XpuPlexRec tmp;
    tmp = list[0];
    list[0] = list[default_plex_rec_index];
    list[default_plex_rec_index] = tmp;
  }

  *numEntriesPtr = rec_count; 
  return(list);
}

void XpuFreePlexList( XpuPlexList list )
{
  if( list )
  {
    XpuPlexRec *curr = list;
  
    /* See the warning abouve about using this implementation detail for
     * checking for the list's end... */
    while( curr->plex != NULL )
    {
      free((void *)curr->plex);
      curr++;
    }   
    free(list);
  }
}

XpuPlexRec *
XpuFindPlexByName( XpuPlexList list, int list_count, const char *plex )
{
  int i;
  
  for( i = 0 ; i < list_count ; i++ )
  {
    XpuPlexRec *curr = &list[i];
    if (!strcasecmp(curr->plex, plex))
      return curr;
  }

  return(NULL);
}

static
int XpuSetContentPlex( Display *pdpy, XPContext pcontext, XPAttributes type, XpuPlexRec *rec )
{
  XpuSetOneAttribute(pdpy, pcontext, type, "*plex", rec->plex, XPAttrMerge);
  return(1);
}

/* Set document plex 
 * Retun error if printer does not support setting an plex
 */
int XpuSetDocPlex( Display *pdpy, XPContext pcontext, XpuPlexRec *rec )
{
  if( (XpuGetSupportedDocAttributes(pdpy, pcontext) & XPUATTRIBUTESUPPORTED_PLEX) == 0 )
    return( 0 );
    
  return XpuSetContentPlex(pdpy, pcontext, XPDocAttr, rec);
}

/* Set page plex
 * Retun error if printer does not support setting an plex or if
 * per-page plex changes are not allowed
 */
int XpuSetPagePlex( Display *pdpy, XPContext pcontext, XpuPlexRec *rec )
{
  if( (XpuGetSupportedPageAttributes(pdpy, pcontext) & XPUATTRIBUTESUPPORTED_PLEX) == 0 )
    return( 0 );
    
  return XpuSetContentPlex(pdpy, pcontext, XPPageAttr, rec);
}


XpuColorspaceList XpuGetColorspaceList( Display *pdpy, XPContext pcontext, int *numEntriesPtr )
{
  XpuColorspaceList list = NULL;
  int               rec_count = 1; /* Allocate one more |XpuColorspaceRec| structure
                                    * as terminator */
  char              namebuf[256];  /* Temporary name buffer for colorspace names */
  int               i;             /* Loop counter */
  int               nvi;           /* Number of visuals */
  Screen           *pscreen;       /* Print screen */
  XVisualInfo       viproto;       /* fill in for getting info */
  XVisualInfo      *vip;           /* retured info */

  pscreen = XpGetScreenOfContext(pdpy, pcontext);

  nvi = 0;
  viproto.screen = XScreenNumberOfScreen(pscreen);
  vip = XGetVisualInfo(pdpy, VisualScreenMask, &viproto, &nvi);
  if (!vip)
  {
    fprintf(stderr, "XpuGetColorspaceList: Internal error: vip == NULL\n");
    return NULL;
  }
  
  for( i = 0 ; i < nvi ; i++ )
  {
    XVisualInfo *vcurr = vip+i;
    char         cbuff[64];
    const char  *class = NULL;

#ifdef USE_MOZILLA_TYPES
    /* Workaround for the current limitation of the gfx/src/xlibrgb code
     * which cannot handle depths > 24bit yet */
    if( vcurr->depth > 24 )
      continue;
#endif /* USE_MOZILLA_TYPES */
 
    rec_count++;
    list = (XpuColorspaceRec *)realloc(list, sizeof(XpuColorspaceRec)*rec_count);
    if( !list )
      return NULL;

    /* ToDO: This needs to be updated for the COLORSPACE X11 extension
     * once it is ready and approved by the XOrg arch board. */
    switch (vcurr->class) {
      case StaticGray:   class = "StaticGray";  break;
      case GrayScale:    class = "GrayScale";   break;
      case StaticColor:  class = "StaticColor"; break;
      case PseudoColor:  class = "PseudoColor"; break;
      case TrueColor:    class = "TrueColor";   break;
      case DirectColor:  class = "DirectColor"; break;
      default: /* Needed for forward compatibility to the COLORSPACE extension */
        sprintf (cbuff, "unknown_class_%x", vcurr->class);
        class = cbuff;
        break;
    }

    if (vcurr->bits_per_rgb == 8)
    {
      sprintf(namebuf, "%s/%dbit", class, vcurr->depth);
    }
    else
    {
      sprintf(namebuf, "%s/%dbit/%dbpg", class, vcurr->depth, vcurr->bits_per_rgb);
    }
    list[rec_count-2].name       = strdup(namebuf);
    list[rec_count-2].visualinfo = *vcurr;
  }  
 
  XFree((char *)vip);

  if( list )
  {
    /* users: DO NOT COUNT ON THIS DETAIL 
     * (this is only to make current impl. of XpuGetResolutionList() easier)
     * We may remove this implementation detail in a later revision of
     * the library! */
    list[rec_count-1].name = NULL;
    rec_count--;
  }
  else
  {
    rec_count = 0;
  }

  *numEntriesPtr = rec_count; 
  return(list);
}

void XpuFreeColorspaceList( XpuColorspaceList list )
{
  if( list )
  { 
    XpuColorspaceRec *curr = list;
  
    /* See the warning abouve about using this implementation detail for
     * checking for the list's end... */
    while( curr->name != NULL )
    {
      free((void *)curr->name);
      curr++;
    }  

    free(list);
  }
}

XpuColorspaceRec *
XpuFindColorspaceByName( XpuColorspaceList list, int list_count, const char *name )
{
  int i;
  
  for( i = 0 ; i < list_count ; i++ )
  {
    XpuColorspaceRec *curr = &list[i];
    if (!strcmp(curr->name, name))
      return curr;
  }

  return(NULL);
}

Bool XpuGetEnableFontDownload( Display *pdpy, XPContext pcontext )
{
  Bool  enableFontDownload;
  char *value;
  
  value = XpGetOneAttribute(pdpy, pcontext, XPPrinterAttr, "xp-listfonts-modes-supported"); 
  if( !value )
  {
    fprintf(stderr, "XpuGetEnableFontDownload: xp-listfonts-modes-supported printer attribute not found.\n");
    return False;
  }
  
  enableFontDownload = (strstr(value, "xp-list-glyph-fonts") != NULL);
  XFree(value);
  return enableFontDownload;
}

int XpuSetEnableFontDownload( Display *pdpy, XPContext pcontext, Bool enableFontDownload )
{
  char *value,
       *newvalue;
  
  value = XpGetOneAttribute(pdpy, pcontext, XPPrinterAttr, "xp-listfonts-modes-supported"); 
  if( !value )
  {
    fprintf(stderr, "XpuSetEnableFontDownload: xp-listfonts-modes-supported printer attribute not found.\n");
    return 0; /* failure */
  }
  
  /* Set "xp-list-glyph-fonts" */
  if( enableFontDownload )
  {
    /* Return success if "xp-list-glyph-fonts" is already set */
    if( strstr(value, "xp-list-glyph-fonts") != NULL )
    {
      XFree(value);
      return 1; /* success */
    }

    newvalue = malloc(strlen(value) + 33);
    if( !newvalue )
    {
      XFree(value);
      return 0; /* failure */
    }

    sprintf(newvalue, "%s xp-list-glyph-fonts", value);
    XpuSetOneAttribute(pdpy, pcontext, XPDocAttr, "*xp-listfonts-modes", newvalue, XPAttrMerge);

    free(newvalue);
    XFree(value);
    return 1; /* success */
  }
  else
  {
    char *s, /* copy string "source" */
         *d; /* copy string "destination" */
    
    /* Return success if "xp-list-glyph-fonts" not set */
    d = strstr(value, "xp-list-glyph-fonts");
    if( d == NULL )
    {
      XFree(value);
      return 1; /* success */
    }

    /* strip "xp-list-glyph-fonts" from |value| */
    s = d+19/*strlen("xp-list-glyph-fonts")*/;
    while( (*d++ = *s++) != '\0' )
      ;

    XpuSetOneAttribute(pdpy, pcontext, XPDocAttr, "*xp-listfonts-modes", value, XPAttrMerge);

    XFree(value);
    return 1; /* success */
  } 
}

/* Return flags to indicate which attributes are supported and which not... */
static
XpuSupportedFlags XpuGetSupportedAttributes( Display *pdpy, XPContext pcontext, XPAttributes type, const char *attribute_name )
{
  char              *value;
  void              *tok_lasts;
  XpuSupportedFlags  flags = 0;
  
  MAKE_STRING_WRITABLE(attribute_name);
  if( attribute_name == NULL )
    return(0);
    
  value = XpGetOneAttribute(pdpy, pcontext, type, STRING_AS_WRITABLE(attribute_name));   
  
  FREE_WRITABLE_STRING(attribute_name);
  
  if( value != NULL )
  {
    const char *s;
    
    for( s = XpuEnumerateXpAttributeValue(value, &tok_lasts) ; s != NULL ; s = XpuEnumerateXpAttributeValue(NULL, &tok_lasts) )
    {
           if( !strcmp(s, "job-name") )                   flags |= XPUATTRIBUTESUPPORTED_JOB_NAME;
      else if( !strcmp(s, "job-owner") )                  flags |= XPUATTRIBUTESUPPORTED_JOB_OWNER;
      else if( !strcmp(s, "notification-profile") )       flags |= XPUATTRIBUTESUPPORTED_NOTIFICATION_PROFILE;
      else if( !strcmp(s, "copy-count") )                 flags |= XPUATTRIBUTESUPPORTED_COPY_COUNT;
      else if( !strcmp(s, "document-format") )            flags |= XPUATTRIBUTESUPPORTED_DOCUMENT_FORMAT;
      else if( !strcmp(s, "content-orientation") )        flags |= XPUATTRIBUTESUPPORTED_CONTENT_ORIENTATION;
      else if( !strcmp(s, "default-printer-resolution") ) flags |= XPUATTRIBUTESUPPORTED_DEFAULT_PRINTER_RESOLUTION;
      else if( !strcmp(s, "default-input-tray") )         flags |= XPUATTRIBUTESUPPORTED_DEFAULT_INPUT_TRAY;
      else if( !strcmp(s, "default-medium") )             flags |= XPUATTRIBUTESUPPORTED_DEFAULT_MEDIUM;
      else if( !strcmp(s, "plex") )                       flags |= XPUATTRIBUTESUPPORTED_PLEX;
      else if( !strcmp(s, "xp-listfonts-modes") )         flags |= XPUATTRIBUTESUPPORTED_LISTFONTS_MODES;
    }
    
    XpuDisposeEnumerateXpAttributeValue(&tok_lasts);
    XFree(value);
  }  
  
  return(flags);
}

XpuSupportedFlags XpuGetSupportedJobAttributes(Display *pdpy, XPContext pcontext)
{
  return XpuGetSupportedAttributes(pdpy, pcontext, XPPrinterAttr, "job-attributes-supported");
}

XpuSupportedFlags XpuGetSupportedDocAttributes(Display *pdpy, XPContext pcontext)
{
  return XpuGetSupportedAttributes(pdpy, pcontext, XPPrinterAttr, "document-attributes-supported");
}

XpuSupportedFlags XpuGetSupportedPageAttributes(Display *pdpy, XPContext pcontext)
{
  return XpuGetSupportedAttributes(pdpy, pcontext, XPPrinterAttr, "xp-page-attributes-supported");
}

/* Encode  string  for  usage  in a Xrm  resource  database  as
 * defined  in  X(7):  [...]  To  allow  a  Value  to  begin
 * with  whitespace,  the  two-character  sequence   ``\space''
 * (backslash  followed by space) is recognized and replaced by
 * a space character, and the two-character  sequence  ``\tab''
 * (backslash  followed  by  horizontal  tab) is recognized and
 * replaced by a horizontal tab character.  To allow a Value to
 * contain   embedded  newline  characters,  the  two-character
 * sequence ``\n'' is recognized  and  replaced  by  a  newline
 * character.   To  allow  a Value to be broken across multiple
 * lines in a text file,  the  two-character  sequence  ``\new-
 * line''  (backslash  followed  by  newline) is recognized and
 * removed from the value.  To allow a Value to  contain  arbi-
 * trary character codes, the four-character sequence ``\nnn'',
 * where  each  n  is  a  digit  character  in  the  range   of
 * ``0''-``7'',  is  recognized and replaced with a single byte
 * that contains the octal value  specified  by  the  sequence.
 * Finally, the two-character sequence ``\\'' is recognized and
 * replaced with a single backslash.
 */
char *XpuResourceEncode( const char *s )
{
  size_t  slen;
  char   *res;
  char   *d;
  int     i,
          c;

  slen = strlen(s);
  res  = malloc(slen*4+1);
  if (!res)
    return NULL;
  
  d = res;
  i = slen;
  while (i--) {
    c = *s++;
    if (c == '\n') {
      if (i) {
        *d++ = '\\';
        *d++ = 'n';
        *d++ = '\\';
        *d++ = '\n';
      }
      else {
        *d++ = '\\';
        *d++ = 'n';
      }
    } else if (c == '\\') {
        *d++ = '\\';
        *d++ = '\\';
    }
    else if ((c < ' ' && c != '\t') ||
            ((unsigned char)c >= 0x7F && (unsigned char)c < 0xA0)) {
        sprintf(d, "\\%03o", (unsigned char)c);
        d += 4;
    }
    else {
        *d++ = c;
    }
  }

  *d = '\0';
  
  return res;
}

#ifdef XXXJULIEN_NOTNOW
char *XpuResourceDecode( const char *str )
{
}
#endif /* XXXJULIEN_NOTNOW */

void XpuResourceFreeString( char *s )
{
  free(s);
}

const char *XpuXmbToCompoundText(Display *dpy, const char *xmbtext)
{
  XTextProperty   xtp;
  int             xcr;
  char           *xtl[2];
  char           *ct;

  if (strlen(xmbtext) == 0)
    return strdup(xmbtext);
  
  memset(&xtp, 0, sizeof(xtp));
  xtl[0] = (char *)xmbtext;
  xtl[1] = NULL;
  
  xcr = XmbTextListToTextProperty(dpy, xtl, 1, XCompoundTextStyle, &xtp);
  
  if (xcr == XNoMemory || xcr == XLocaleNotSupported)
  {
    fprintf(stderr, "XpuXmbToCompoundText: XmbTextListToTextProperty failure.\n");
    return strdup(xmbtext);
  }

  /* Did conversion succeed (some unconvertible characters
   * are not a problem) ? */
  if ( !((xcr == Success) || (xcr > 0)) ||
       (xtp.value == NULL))
  {
    fprintf(stderr, "XpuXmbToCompoundText: XmbTextListToTextProperty failure 2.\n");
    return strdup(xmbtext);
  }
  
  ct = malloc(xtp.nitems+1);
  if (!ct)
  {
    XFree(xtp.value);
    return NULL;
  }
  memcpy(ct, xtp.value, xtp.nitems);
  ct[xtp.nitems] = '\0';  

  XFree(xtp.value);
  
  return ct;
}

void XpuFreeCompundTextString( const char *s )
{
  free((void *)s);
}

const char *XpuCompoundTextToXmb(Display *dpy, const char *ct)
{
  XTextProperty   xtp;
  int             xcr;
  char          **xtl = NULL;
  int             xtl_count = 0;
  char           *xmb;
  int             xmb_len = 0;
  int             i;

  if (strlen(ct) == 0)
    return strdup(ct);
    
  xtp.value    = (unsigned char *)ct;
  xtp.nitems   = strlen(ct); 
  xtp.encoding = XInternAtom(dpy, "COMPOUND_TEXT", False);
  xtp.format   = 8;
  
  xcr = XmbTextPropertyToTextList(dpy, &xtp, &xtl, &xtl_count);
  
  if (xcr == XNoMemory || xcr == XLocaleNotSupported)
  {
    fprintf(stderr, "XpuCompoundTextToXmb: XmbTextPropertyToTextList failure 1.\n");
    return strdup(ct);
  }

  /* Did conversion succeed (some unconvertible characters
   * are not a problem) ? */
  if ( !((xcr == Success) || (xcr > 0)) ||
       (xtl == NULL))
  {
    fprintf(stderr, "XpuCompoundTextToXmb: XmbTextPropertyToTextList failure 2.\n");
    return strdup(ct);
  }
   
  for (i = 0; i < xtl_count; i++)
  {
    xmb_len += strlen(xtl[i]);
  }
  xmb = malloc (xmb_len + 1);
  if (!xmb)
  {
    XFreeStringList(xtl);
    return NULL;
  }
  xmb[0] = '\0'; /* Catch zero-length case */
  for (i = 0; i < xtl_count; i++)
  {
    strcat(xmb, xtl[i]);
  }
  
  XFreeStringList(xtl); 
  
  return xmb;
}

void XpuFreeXmbString( const char *s )
{
  free((void *)s);
}

/* EOF. */