/*
 * "$Id$"
 *
 *   Print plug-in for the GIMP.
 *
 *   Copyright 1997-2000 Michael Sweet (mike@easysw.com) and
 *	Robert Krawitz (rlk@alum.mit.edu)
 *
 *   This program is free software; you can redistribute it and/or modify it
 *   under the terms of the GNU General Public License as published by the Free
 *   Software Foundation; either version 2 of the License, or (at your option)
 *   any later version.
 *
 *   This program is distributed in the hope that it will be useful, but
 *   WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 *   for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "print_gimp.h"

#ifdef GIMP_1_0
#include <math.h>
#endif

#include <signal.h>
#include <ctype.h>
#include <sys/wait.h>
#ifdef __EMX__
#define INCL_DOSDEVICES
#define INCL_DOSERRORS
#include <os2.h>
#endif

#include <unistd.h>

#include "print-intl.h"

/*
 * Local functions...
 */

static void	printrc_load(void);
void	        printrc_save(void);
static int	compare_printers(plist_t *p1, plist_t *p2);
static void	get_system_printers(void);

static void	query (void);
static void	run (char *, int, GimpParam *, int *, GimpParam **);
static int	do_print_dialog (char *proc_name);

#ifndef GIMP_1_0
extern void     gimp_create_main_window (void);
#endif
#ifndef NEW_UI_ONLY
extern void     gtk_create_main_window (void);
#endif

#if 0
static void	cleanupfunc(void);
#endif

/*
 * Globals...
 */

GimpPlugInInfo PLUG_IN_INFO =		/* Plug-in information */
{
  NULL,  /* init_proc  */
  NULL,  /* quit_proc  */
  query, /* query_proc */
  run,   /* run_proc   */
};

vars_t vars =
{
	"",			/* Name of file or command to print to */
	"ps2",			/* Name of printer "driver" */
	"",			/* Name of PPD file */
	OUTPUT_COLOR,		/* Color or grayscale output */
	"",			/* Output resolution */
	"",			/* Size of output media */
	"",			/* Type of output media */
	"",			/* Source of output media */
	"",			/* Ink type */
	"",			/* Dither algorithm */
	1.0,			/* Output brightness */
	100.0,			/* Scaling (100% means entire printable area, */
				/*          -XXX means scale by PPI) */
	-1,			/* Orientation (-1 = automatic) */
	-1,			/* X offset (-1 = center) */
	-1,			/* Y offset (-1 = center) */
	1.0,			/* Screen gamma */
	1.0,			/* Contrast */
	1.0,			/* Cyan */
	1.0,			/* Magenta */
	1.0,			/* Yellow */
	0,			/* Linear */
	1.0,			/* Output saturation */
	1.0,			/* Density */
	IMAGE_CONTINUOUS,	/* Image type */
	0,			/* Unit 0=Inch */
	1.0,			/* Application gamma placeholder */
	0,			/* Page width */
	0			/* Page height */
};

int		plist_current = 0,	/* Current system printer */
		plist_count = 0;	/* Number of system printers */
plist_t		*plist;			/* System printers */

int		saveme = FALSE;		/* True if print should proceed */
int		runme = FALSE;		/* True if print should proceed */
const printer_t *current_printer = 0;	/* Current printer index */
gint32          image_ID;	        /* image ID */

const char *image_filename;
int image_width;
int image_height;

static void
check_plist(int count)
{
  static int current_plist_size = 0;
  if (count <= current_plist_size)
    return;
  else if (current_plist_size == 0)
    {
      current_plist_size = count;
      plist = malloc(current_plist_size * sizeof(plist_t));
    }
  else
    {
      current_plist_size *= 2;
      if (current_plist_size < count)
	current_plist_size = count;
      plist = realloc(plist, current_plist_size * sizeof(plist_t));
    }
}

/*
 * 'main()' - Main entry - just call gimp_main()...
 */

#if 0
int
main(int  argc,		/* I - Number of command-line args */
     char *argv[])	/* I - Command-line args */
{
  return (gimp_main(argc, argv));
}
#else
MAIN()
#endif

static int print_finished = 0;

#if 0
void
cleanupfunc(void)
{
}
#endif

/*
 * 'query()' - Respond to a plug-in query...
 */

static void
query (void)
{
  static GimpParamDef	args[] =
  {
    { GIMP_PDB_INT32,	"run_mode",	"Interactive, non-interactive" },
    { GIMP_PDB_IMAGE,	"image",	"Input image" },
    { GIMP_PDB_DRAWABLE,	"drawable",	"Input drawable" },
    { GIMP_PDB_STRING,	"output_to",	"Print command or filename (| to pipe to command)" },
    { GIMP_PDB_STRING,	"driver",	"Printer driver short name" },
    { GIMP_PDB_STRING,	"ppd_file",	"PPD file" },
    { GIMP_PDB_INT32,	"output_type",	"Output type (0 = gray, 1 = color)" },
    { GIMP_PDB_STRING,	"resolution",	"Resolution (\"300\", \"720\", etc.)" },
    { GIMP_PDB_STRING,	"media_size",	"Media size (\"Letter\", \"A4\", etc.)" },
    { GIMP_PDB_STRING,	"media_type",	"Media type (\"Plain\", \"Glossy\", etc.)" },
    { GIMP_PDB_STRING,	"media_source",	"Media source (\"Tray1\", \"Manual\", etc.)" },
    { GIMP_PDB_FLOAT,	"brightness",	"Brightness (0-400%)" },
    { GIMP_PDB_FLOAT,	"scaling",	"Output scaling (0-100%, -PPI)" },
    { GIMP_PDB_INT32,	"orientation",	"Output orientation (-1 = auto, 0 = portrait, 1 = landscape)" },
    { GIMP_PDB_INT32,	"left",		"Left offset (points, -1 = centered)" },
    { GIMP_PDB_INT32,	"top",		"Top offset (points, -1 = centered)" },
    { GIMP_PDB_FLOAT,	"gamma",	"Output gamma (0.1 - 3.0)" },
    { GIMP_PDB_FLOAT,	"contrast",	"Contrast" },
    { GIMP_PDB_FLOAT,	"cyan",		"Cyan level" },
    { GIMP_PDB_FLOAT,	"magenta",	"Magenta level" },
    { GIMP_PDB_FLOAT,	"yellow",		"Yellow level" },
    { GIMP_PDB_INT32,	"linear",	"Linear output (0 = normal, 1 = linear)" },
    { GIMP_PDB_INT32,	"image_type",	"Image type (0 = line art, 1 = solid tones, 2 = continuous tone, 3 = monochrome)"},
    { GIMP_PDB_FLOAT,	"saturation",	"Saturation (0-1000%)" },
    { GIMP_PDB_FLOAT,	"density",	"Density (0-200%)" },
    { GIMP_PDB_STRING,	"ink_type",	"Type of ink or cartridge" },
    { GIMP_PDB_STRING,	"dither_algorithm", "Dither algorithm" },
    { GIMP_PDB_INT32,	"unit",		"Unit 0=Inches 1=Metric" },
  };
  static gint nargs = sizeof(args) / sizeof(args[0]);

  static gchar *blurb = "This plug-in prints images from The GIMP.";
  static gchar *help  = "Prints images to PostScript, PCL, or ESC/P2 printers.";
  static gchar *auth  = "Michael Sweet <mike@easysw.com> and Robert Krawitz <rlk@alum.mit.edu>";
  static gchar *copy  = "Copyright 1997-2000 by Michael Sweet and Robert Krawitz";
  static gchar *types = "RGB*,GRAY*,INDEXED*";

  gimp_install_procedure ("file_print_gimp",
			  blurb, help, auth, copy,
			  PLUG_IN_VERSION,
			  N_("<Image>/File/Print..."),
			  types,
			  GIMP_PLUGIN,
			  nargs, 0,
			  args, NULL);
}


#ifdef __EMX__
static char *
get_tmp_filename()
{
  char *tmp_path, *s, filename[80];

  tmp_path = getenv("TMP");
  if (tmp_path == NULL)
    tmp_path = "";

  sprintf(filename, "gimp_print_tmp.%d", getpid());
  s = tmp_path = g_strconcat(tmp_path, "\\", filename, NULL);
  if (!s)
    return NULL;
  for ( ; *s; s++)
    if (*s == '/') *s = '\\';
  return tmp_path;
}
#endif

/*
 * 'usr1_handler()' - Make a note when we receive SIGUSR1.
 */

static volatile int usr1_interrupt;

static void
usr1_handler (int signal)
{
  usr1_interrupt = 1;
}

/*
 * 'run()' - Run the plug-in...
 */

/* #define DEBUG_STARTUP */


#ifdef DEBUG_STARTUP
volatile int SDEBUG = 1;
#endif

static void
run (char   *name,		/* I - Name of print program. */
     int    nparams,		/* I - Number of parameters passed in */
     GimpParam *param,		/* I - Parameter values */
     int    *nreturn_vals,	/* O - Number of return values */
     GimpParam **return_vals)	/* O - Return values */
{
  GimpDrawable	*drawable;	/* Drawable for image */
  GimpRunModeType	 run_mode;	/* Current run mode */
  FILE		*prn;		/* Print file/command */
  int		 ncolors;	/* Number of colors in colormap */
  GimpParam	*values;	/* Return values */
#ifdef __EMX__
  char		*tmpfile;	/* temp filename */
#endif
  gint32         drawable_ID;   /* drawable ID */
#ifndef GIMP_1_0
  GimpExportReturnType export = GIMP_EXPORT_CANCEL;    /* return value of gimp_export_image() */
#endif
  int		ppid = getpid (), /* PID of plugin */
		opid,		/* PID of output process */
		cpid = 0,	/* PID of control/monitor process */
		pipefd[2];	/* Fds of the pipe connecting all the above */
  int		dummy;
#ifdef DEBUG_STARTUP
  while (SDEBUG)
    ;
#endif

#ifdef INIT_I18N_UI
  INIT_I18N_UI();
#else
  INIT_LOCALE ("gimp-print");
#endif

  /*
   * Initialize parameter data...
   */

  current_printer = get_printer_by_index (0);
  run_mode = (GimpRunModeType)param[0].data.d_int32;

  values = g_new (GimpParam, 1);

  values[0].type          = GIMP_PDB_STATUS;
  values[0].data.d_status = GIMP_PDB_SUCCESS;

  *nreturn_vals = 1;
  *return_vals  = values;

  image_ID = param[1].data.d_int32;
  drawable_ID = param[2].data.d_int32;

  image_filename = gimp_image_get_filename (image_ID);
  if (strchr(image_filename, '/'))
    image_filename = strrchr(image_filename, '/') + 1;

#ifndef GIMP_1_0
  /*  eventually export the image */
  switch (run_mode)
    {
    case GIMP_RUN_INTERACTIVE:
    case GIMP_RUN_WITH_LAST_VALS:
      gimp_ui_init ("print", TRUE);
      export = gimp_export_image (&image_ID, &drawable_ID, "Print",
				  (GIMP_EXPORT_CAN_HANDLE_RGB |
				   GIMP_EXPORT_CAN_HANDLE_GRAY |
				   GIMP_EXPORT_CAN_HANDLE_INDEXED |
				   GIMP_EXPORT_CAN_HANDLE_ALPHA));
      if (export == GIMP_EXPORT_CANCEL)
	{
	  *nreturn_vals = 1;
	  values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
	  return;
	}
      break;
    default:
      break;
    }
#endif

  /*
   * Get drawable...
   */

  drawable = gimp_drawable_get (drawable_ID);

  image_width  = drawable->width;
  image_height = drawable->height;

  /*
   * See how we will run
   */

  switch (run_mode)
    {
    case GIMP_RUN_INTERACTIVE:
      /*
       * Get information from the dialog...
       */

      if (!do_print_dialog (name))
	goto cleanup;
      break;

    case GIMP_RUN_NONINTERACTIVE:
      /*
       * Make sure all the arguments are present...
       */
      if (nparams < 11)
	values[0].data.d_status = GIMP_PDB_CALLING_ERROR;
      else
	{
	  strcpy (vars.output_to, param[3].data.d_string);
	  strcpy (vars.driver, param[4].data.d_string);
	  strcpy (vars.ppd_file, param[5].data.d_string);
	  vars.output_type = param[6].data.d_int32;
	  strcpy (vars.resolution, param[7].data.d_string);
	  strcpy (vars.media_size, param[8].data.d_string);
	  strcpy (vars.media_type, param[9].data.d_string);
	  strcpy (vars.media_source, param[10].data.d_string);

          if (nparams > 11)
	    vars.brightness = param[11].data.d_int32;
	  else
	    vars.brightness = 100;

          if (nparams > 12)
            vars.scaling = param[12].data.d_float;
          else
            vars.scaling = 100.0;

          if (nparams > 13)
            vars.orientation = param[13].data.d_int32;
          else
            vars.orientation = -1;

          if (nparams > 14)
            vars.left = param[14].data.d_int32;
          else
            vars.left = -1;

          if (nparams > 15)
            vars.top = param[15].data.d_int32;
          else
            vars.top = -1;

          if (nparams > 16)
            vars.gamma = param[16].data.d_float;
          else
            vars.gamma = 1.0;

          if (nparams > 17)
	    vars.contrast = param[17].data.d_int32;
	  else
	    vars.contrast = 100;

          if (nparams > 18)
	    vars.cyan = param[18].data.d_int32;
	  else
	    vars.cyan = 100;

          if (nparams > 19)
	    vars.magenta = param[19].data.d_int32;
	  else
	    vars.magenta = 100;

          if (nparams > 20)
	    vars.yellow = param[20].data.d_int32;
	  else
	    vars.yellow = 100;

          if (nparams > 21)
            vars.linear = param[21].data.d_int32;
          else
            vars.linear = 0;

          if (nparams > 22)
            vars.image_type = param[22].data.d_int32;
          else
            vars.image_type = IMAGE_CONTINUOUS;

          if (nparams > 23)
            vars.saturation = param[23].data.d_float;
          else
            vars.saturation = 1.0;

          if (nparams > 24)
            vars.density = param[24].data.d_float;
          else
            vars.density = 1.0;

	  if (nparams > 25)
	    strcpy (vars.ink_type, param[25].data.d_string);
	  else
	    memset (vars.ink_type, 0, 64);

	  if (nparams > 26)
	    strcpy (vars.dither_algorithm, param[26].data.d_string);
	  else
	    memset (vars.dither_algorithm, 0, 64);

          if (nparams > 27)
            vars.unit = param[27].data.d_int32;
          else
            vars.unit = 0;
	}

      current_printer = get_printer_by_driver (vars.driver);
      break;

    case GIMP_RUN_WITH_LAST_VALS:
      /*
       * Possibly retrieve data...
       */
      gimp_get_data (PLUG_IN_NAME, &vars);
      vars.page_width = 0;
      vars.page_height = 0;

      current_printer = get_printer_by_driver (vars.driver);
      break;

    default:
      values[0].data.d_status = GIMP_PDB_CALLING_ERROR;
      break;;
    }

  /*
   * Print the image...
   */
  if (values[0].data.d_status == GIMP_PDB_SUCCESS)
    {
      /*
       * Set the tile cache size...
       */

      if (drawable->height > drawable->width)
	gimp_tile_cache_ntiles ((drawable->height + gimp_tile_width () - 1) /
				gimp_tile_width () + 1);
      else
	gimp_tile_cache_ntiles ((drawable->width + gimp_tile_width () - 1) /
				gimp_tile_width () + 1);

      /*
       * Open the file/execute the print command...
       */

      if (plist_current > 0)
#ifndef __EMX__
      {
	/*
	 * The following IPC code is only necessary because the GIMP kills
	 * plugins with SIGKILL if its "Cancel" button is pressed; this
	 * gives the plugin no chance whatsoever to clean up after itself.
	 */
	usr1_interrupt = 0;
	signal (SIGUSR1, usr1_handler);
	if (pipe (pipefd) != 0) {
	  prn = NULL;
	} else {
	  cpid = fork ();
	  if (cpid < 0) {
	    prn = NULL;
	  } else if (cpid == 0) {
	    /* LPR monitor process.  Printer output is piped to us. */
	    opid = fork ();
	    if (opid < 0) {
	      /* Errors will cause the plugin to get a SIGPIPE.  */
	      exit (1);
	    } else if (opid == 0) {
	      dup2 (pipefd[0], 0);
	      close (pipefd[0]);
	      close (pipefd[1]);
	      execl("/bin/sh", "/bin/sh", "-c", vars.output_to, NULL);
	      /* NOTREACHED */
	      exit (1);
	    } else {
	      /*
	       * If the print plugin gets SIGKILLed by gimp, we kill lpr
	       * in turn.  If the plugin signals us with SIGUSR1 that it's
	       * finished printing normally, we close our end of the pipe,
	       * and go away.
	       */
	      close (pipefd[0]);
	      while (usr1_interrupt == 0) {
	        if (kill (ppid, 0) < 0) {
		  /* The print plugin has been killed!  */
		  kill (opid, SIGTERM);
		  waitpid (opid, &dummy, 0);
		  close (pipefd[1]);
		  /*
		   * We do not want to allow cleanup before exiting.
		   * The exiting parent has already closed the connection
		   * to the X server; if we try to clean up, we'll notice
		   * that fact and complain.
		   */
		  _exit (0);
	        }
	        sleep (5);
	      }
	      /* We got SIGUSR1.  */
	      close (pipefd[1]);
	      /*
	       * We do not want to allow cleanup before exiting.
	       * The exiting parent has already closed the connection
	       * to the X server; if we try to clean up, we'll notice
	       * that fact and complain.
	       */
	      _exit (0);
	    }
	  } else {
	    close (pipefd[0]);
	    /* Parent process.  We generate the printer output. */
	    prn = fdopen (pipefd[1], "w");
	    /* and fall through... */
	  }
	}
      }
#else
      /* OS/2 PRINT command doesn't support print from stdin, use temp file */
      prn = (tmpfile = get_tmp_filename ()) ? fopen (tmpfile, "w") : NULL;
#endif
      else
	prn = fopen (vars.output_to, "wb");

      if (prn != NULL)
	{
	  Image image = Image_GDrawable_new(drawable);
	  vars.app_gamma = gimp_gamma();
	  merge_printvars(&vars, &(current_printer->printvars));

	  /*
	   * Is the image an Indexed type?  If so we need the colormap...
	   */

	  if (gimp_image_base_type (image_ID) == GIMP_INDEXED)
	    vars.cmap = gimp_image_get_cmap (image_ID, &ncolors);
	  else
	    vars.cmap    = NULL;

	  /*
	   * Finally, call the print driver to send the image to the printer
	   * and close the output file/command...
	   */

	  if (verify_printer_params(current_printer, &vars))
	    (*current_printer->print) (current_printer, 1, prn, image, &vars);
	  else
	    values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;

	  if (plist_current > 0)
#ifndef __EMX__
	  {
	    fclose (prn);
	    kill (cpid, SIGUSR1);
	    waitpid (cpid, &dummy, 0);
	  }
#else
	  { /* PRINT temp file */
	    char *s;
	    fclose (prn);
	    s = g_strconcat (vars.output_to, tmpfile, NULL);
	    if (system(s) != 0)
	      values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
	    g_free (s);
	    remove (tmpfile);
	    g_free( tmpfile);
	  }
#endif
	  else
	    fclose (prn);
	  print_finished = 1;
	}
      else
	values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;

      /*
       * Store data...
       */

      if (run_mode == GIMP_RUN_INTERACTIVE)
	gimp_set_data (PLUG_IN_NAME, &vars, sizeof (vars));
    }

  /*
   * Detach from the drawable...
   */
  gimp_drawable_detach (drawable);


 cleanup:
#ifndef GIMP_1_0
  if (export == GIMP_EXPORT_EXPORT)
    gimp_image_delete (image_ID);
#else
  ; /* MRS: empty statement to suppress compiler warning */
#endif
}

/*
 * 'do_print_dialog()' - Pop up the print dialog...
 */

static gint
do_print_dialog (gchar *proc_name)
{
#ifdef GIMP_1_0
  gchar **argv;
  gint    argc;

  argc    = 1;
  argv    = g_new (gchar *, 1);
  argv[0] = g_strdup ("print");

  gtk_init (&argc, &argv);
  gtk_rc_parse (gimp_gtkrc ());

  gdk_set_use_xshm (gimp_use_xshm());
#endif

  /*
   * Get printrc options...
   */
  printrc_load ();

  /*
   * Print dialog window...
   */
#ifdef NEW_UI_ONLY
  gimp_create_main_window();
#elif defined(GIMP_1_0)
  gtk_create_main_window ();
#else
  if (!strcmp (proc_name, "file_print_gimp"))
    gimp_create_main_window ();
  else
    gtk_create_main_window ();
#endif

  gtk_main ();
  gdk_flush ();

  /*
   * Set printrc options...
   */
  if (saveme)
    printrc_save ();

  /*
   * Return ok/cancel...
   */
  return (runme);
}

static void
initialize_printer(plist_t *printer)
{
  const vars_t *def = print_default_settings();
  printer->name[0] = '\0';
  printer->active=0;
  memcpy(&(printer->v), def, sizeof(vars_t));
}

#define GET_MANDATORY_STRING_PARAM(param)		\
do {							\
  if ((commaptr = strchr(lineptr, ',')) == NULL)	\
    continue;						\
  strncpy(key.param, lineptr, commaptr - line);		\
  key.param[commaptr - lineptr] = '\0';			\
  lineptr = commaptr + 1;				\
} while (0)

#define GET_MANDATORY_INT_PARAM(param)			\
do {							\
  if ((commaptr = strchr(lineptr, ',')) == NULL)	\
    continue;						\
  key.param = atoi(lineptr);				\
  lineptr = commaptr + 1;				\
} while (0)

#define GET_OPTIONAL_STRING_PARAM(param)			\
do {								\
  if ((commaptr = strchr(lineptr, ',')) == NULL)		\
    {								\
      strcpy(key.v.param, lineptr);				\
      keepgoing = 0;						\
      key.v.param[strlen(key.v.param) - 1] = '\0';		\
    }								\
  else								\
    {								\
      strncpy(key.v.param, lineptr, commaptr - lineptr);	\
      key.v.param[commaptr - lineptr] = '\0';			\
      lineptr = commaptr + 1;					\
    }								\
} while (0)

#define GET_OPTIONAL_INT_PARAM(param)					\
do {									\
  if ((keepgoing == 0) || ((commaptr = strchr(lineptr, ',')) == NULL))	\
    {									\
      keepgoing = 0;							\
    }									\
  else									\
    {									\
      key.v.param = atoi(lineptr);					\
      lineptr = commaptr + 1;						\
    }									\
} while (0)

#define GET_OPTIONAL_FLOAT_PARAM(param)					\
do {									\
  if ((keepgoing == 0) || ((commaptr = strchr(lineptr, ',')) == NULL))	\
    {									\
      keepgoing = 0;							\
    }									\
  else									\
    {									\
      const vars_t *maxvars = print_maximum_settings();			\
      const vars_t *minvars = print_minimum_settings();			\
      const vars_t *defvars = print_default_settings();			\
      key.v.param = atof(lineptr);					\
      if (key.v.param > 0 &&						\
	  (key.v.param > 2 * maxvars->param ||				\
	   key.v.param < minvars->param))				\
	key.v.param = defvars->param;					\
      lineptr = commaptr + 1;						\
    }									\
} while (0)

static void *
psearch(const void *key, const void *base, size_t nmemb, size_t size,
	int (*compar)(const void *, const void *))
{
  int i;
  const char *cbase = (const char *) base;
  for (i = 0; i < nmemb; i++)
    {
      if ((*compar)(key, (const void *) cbase) == 0)
	return (void *) cbase;
      cbase += size;
    }
  return NULL;
}

/*
 * 'printrc_load()' - Load the printer resource configuration file.
 */
void
printrc_load(void)
{
  int		i;		/* Looping var */
  FILE		*fp;		/* Printrc file */
  char		*filename;	/* Its name */
  char		line[1024],	/* Line in printrc file */
		*lineptr,	/* Pointer in line */
		*commaptr;	/* Pointer to next comma */
  plist_t	*p = 0,		/* Current printer */
		key;		/* Search key */
#if (GIMP_MINOR_VERSION == 0)
  char		*home;		/* Home dir */
#endif
  int		format = 0;	/* rc file format version */
  int		system_printers; /* printer count before reading printrc */
  char *	current_printer = 0; /* printer to select */

  check_plist(1);

 /*
  * Get the printer list...
  */

  get_system_printers();

  system_printers = plist_count - 1;

 /*
  * Generate the filename for the current user...
  */

#if (GIMP_MINOR_VERSION == 0)
  home = getenv("HOME");
  if (home == NULL)
    filename=g_strdup("/.gimp/printrc");
  else
    filename = malloc(strlen(home) + 15);
    sprintf(filename, "%s/.gimp/printrc", home);
#else
  filename = gimp_personal_rc_file ("printrc");
#endif

#ifdef __EMX__
  _fnslashify(filename);
#endif

#ifndef __EMX__
  if ((fp = fopen(filename, "r")) != NULL)
#else
  if ((fp = fopen(filename, "rt")) != NULL)
#endif
  {
   /*
    * File exists - read the contents and update the printer list...
    */

    (void) memset(&key, 0, sizeof(plist_t));
    (void) memset(line, 0, 1024);
    while (fgets(line, sizeof(line), fp) != NULL)
    {
      int keepgoing = 1;
      if (line[0] == '#')
      {
	if (strncmp("#PRINTRCv", line, 9) == 0)
	{
#ifdef DEBUG
          printf("Found printrc version tag: `%s'\n", line);
          printf("Version number: `%s'\n", &(line[9]));
#endif
	  (void) sscanf(&(line[9]), "%d", &format);
	}
        continue;	/* Comment */
      }
      if (format == 0)
      {
       /*
	* Read old format printrc lines...
	*/

        initialize_printer(&key);
        lineptr = line;

       /*
        * Read the command-delimited printer definition data.  Note that
        * we can't use sscanf because %[^,] fails if the string is empty...
        */

        GET_MANDATORY_STRING_PARAM(name);
        GET_MANDATORY_STRING_PARAM(v.output_to);
        GET_MANDATORY_STRING_PARAM(v.driver);

        if (! get_printer_by_driver(key.v.driver))
	  continue;

        GET_MANDATORY_STRING_PARAM(v.ppd_file);
        GET_MANDATORY_INT_PARAM(v.output_type);
        GET_MANDATORY_STRING_PARAM(v.resolution);
        GET_MANDATORY_STRING_PARAM(v.media_size);
        GET_MANDATORY_STRING_PARAM(v.media_type);

        GET_OPTIONAL_STRING_PARAM(media_source);
        GET_OPTIONAL_FLOAT_PARAM(brightness);
        GET_OPTIONAL_FLOAT_PARAM(scaling);
        GET_OPTIONAL_INT_PARAM(orientation);
        GET_OPTIONAL_INT_PARAM(left);
        GET_OPTIONAL_INT_PARAM(top);
        GET_OPTIONAL_FLOAT_PARAM(gamma);
        GET_OPTIONAL_FLOAT_PARAM(contrast);
        GET_OPTIONAL_FLOAT_PARAM(cyan);
        GET_OPTIONAL_FLOAT_PARAM(magenta);
        GET_OPTIONAL_FLOAT_PARAM(yellow);
        GET_OPTIONAL_INT_PARAM(linear);
        GET_OPTIONAL_INT_PARAM(image_type);
        GET_OPTIONAL_FLOAT_PARAM(saturation);
        GET_OPTIONAL_FLOAT_PARAM(density);
        GET_OPTIONAL_STRING_PARAM(ink_type);
        GET_OPTIONAL_STRING_PARAM(dither_algorithm);
        GET_OPTIONAL_INT_PARAM(unit);

/*
 * The format of the list is the File printer followed by a qsort'ed list
 * of system printers. So, if we want to update the file printer, it is
 * always first in the list, else call psearch.
 */
        if ((strcmp(key.name, _("File")) == 0) && (strcmp(plist[0].name,
	     _("File")) == 0))
	  {
#ifdef DEBUG
	    printf("Updated File printer directly\n");
#endif
	    p = &plist[0];
	    memcpy(p, &key, sizeof(plist_t));
	    p->active = 1;
	  }
        else
	  {
            if ((p = psearch(&key, plist + 1, plist_count - 1, sizeof(plist_t),
                         (int (*)(const void *, const void *))compare_printers))
	        != NULL)
	      {
#ifdef DEBUG
	        printf("Updating printer %s.\n", key.name);
#endif
	        memcpy(p, &key, sizeof(plist_t));
	        p->active = 1;
	      }
            else
    	      {
#ifdef DEBUG
                fprintf(stderr, "Adding new printer from printrc file: %s\n",
                  key.name);
#endif
	        check_plist(plist_count + 1);
	        p = plist + plist_count;
	        memcpy(p, &key, sizeof(plist_t));
	        p->active = 0;
	        plist_count++;
	      }
	  }
      }
      else if (format == 1)
      {
       /*
	* Read new format printrc lines...
	*/

	char *keyword, *end, *value;

	keyword = line;
	for (keyword = line; isspace(*keyword); keyword++)
	{
	  /* skip initial spaces... */
	}
	if (!isalpha(*keyword))
	  continue;
	for (end = keyword; isalnum(*end) || *end == '-'; end++)
	{
	  /* find end of keyword... */
	}
	value = end;
	while (isspace(*value)) {
	  /* skip over white space... */
	  value++;
	}
	if (*value != ':')
	  continue;
	value++;
	*end = '\0';
	while (isspace(*value)) {
	  /* skip over white space... */
	  value++;
	}
	for (end = value; *end && *end != '\n'; end++)
	{
	  /* find end of line... */
	}
	*end = '\0';
#ifdef DEBUG
        printf("Keyword = `%s', value = `%s'\n", keyword, value);
#endif
	if (strcasecmp("current-printer", keyword) == 0) {
	  if (current_printer)
	    free (current_printer);
	  current_printer = strdup(value);
	} else if (strcasecmp("printer", keyword) == 0) {
	  /* Switch to printer named VALUE */
	  if (strcmp(_("File"), key.name) == 0
	      && strcmp(plist[0].name, _("File")) == 0)
	  {
	    if (get_printer_by_driver(key.v.driver))
	      {
		p = &plist[0];
		memcpy(p, &key, sizeof(plist_t));
		p->active = 1;
	      }
	  }
	  else
	  {
	    if (get_printer_by_driver(key.v.driver))
	      {
		p = psearch(&key, plist + 1, plist_count - 1,
			    sizeof(plist_t),
			    (int (*)(const void *, const void *)) compare_printers);
		if (p == NULL)
		  {
		    check_plist(plist_count + 1);
		    p = plist + plist_count;
		    plist_count++;
		    memcpy(p, &key, sizeof(plist_t));
		    p->active = 0;
		  }
		else
		  {
		    memcpy(p, &key, sizeof(plist_t));
		    p->active = 1;
		  }
	      }
	  }
	  initialize_printer(&key);
	  strncpy(key.name, value, 127);
	} else if (strcasecmp("destination", keyword) == 0) {
	  strncpy(key.v.output_to, value, 255);
	} else if (strcasecmp("driver", keyword) == 0) {
	  strncpy(key.v.driver, value, 63);
	} else if (strcasecmp("ppd-file", keyword) == 0) {
	  strncpy(key.v.ppd_file, value, 256);
	} else if (strcasecmp("output-type", keyword) == 0) {
	  key.v.output_type = atoi(value);
	} else if (strcasecmp("resolution", keyword) == 0) {
	  strncpy(key.v.resolution, value, 63);
	} else if (strcasecmp("media-size", keyword) == 0) {
	  strncpy(key.v.media_size, value, 63);
	} else if (strcasecmp("media-type", keyword) == 0) {
	  strncpy(key.v.media_type, value, 63);
	} else if (strcasecmp("media-source", keyword) == 0) {
	  strncpy(key.v.media_source, value, 63);
	} else if (strcasecmp("brightness", keyword) == 0) {
	  key.v.brightness = atof(value);
	} else if (strcasecmp("scaling", keyword) == 0) {
	  key.v.scaling = atof(value);
	} else if (strcasecmp("orientation", keyword) == 0) {
	  key.v.orientation = atoi(value);
	} else if (strcasecmp("left", keyword) == 0) {
	  key.v.left = atoi(value);
	} else if (strcasecmp("top", keyword) == 0) {
	  key.v.top = atoi(value);
	} else if (strcasecmp("gamma", keyword) == 0) {
	  key.v.gamma = atof(value);
	} else if (strcasecmp("contrast", keyword) == 0) {
	  key.v.contrast = atof(value);
	} else if (strcasecmp("cyan", keyword) == 0) {
	  key.v.cyan = atof(value);
	} else if (strcasecmp("magenta", keyword) == 0) {
	  key.v.magenta = atof(value);
	} else if (strcasecmp("yellow", keyword) == 0) {
	  key.v.yellow = atof(value);
	} else if (strcasecmp("linear", keyword) == 0) {
	  key.v.linear = atoi(value);
	} else if (strcasecmp("image-type", keyword) == 0) {
	  key.v.image_type = atoi(value);
	} else if (strcasecmp("saturation", keyword) == 0) {
	  key.v.saturation = atof(value);
	} else if (strcasecmp("density", keyword) == 0) {
	  key.v.density = atof(value);
	} else if (strcasecmp("ink-type", keyword) == 0) {
	  strncpy(key.v.ink_type, value, 63);
	} else if (strcasecmp("dither-algorithm", keyword) == 0) {
	  strncpy(key.v.dither_algorithm, value, 63);
	} else if (strcasecmp("unit", keyword) == 0) {
	  key.v.unit = atoi(value);
	} else {
	  /* Unrecognised keyword; ignore it... */
#if 1
          printf("Unrecognised keyword `%s' in printrc; value `%s'\n", keyword, value);
#endif
	}
      }
      else
      {
       /*
        * We cannot read this file format...
        */
      }
    }
    if (format > 0)
      {
	if (strcmp(_("File"), key.name) == 0
	    && strcmp(plist[0].name, _("File")) == 0)
	  {
	    if (get_printer_by_driver(key.v.driver))
	      {
		p = &plist[0];
		memcpy(p, &key, sizeof(plist_t));
		p->active = 1;
	      }
	  }
	else
	  {
	    if (get_printer_by_driver(key.v.driver))
	      {
		p = psearch(&key, plist + 1, plist_count - 1,
			    sizeof(plist_t),
			    (int (*)(const void *, const void *)) compare_printers);
		if (p == NULL)
		  {
		    check_plist(plist_count + 1);
		    p = plist + plist_count;
		    plist_count++;
		    memcpy(p, &key, sizeof(plist_t));
		    p->active = 0;
		  }
		else
		  {
		    memcpy(p, &key, sizeof(plist_t));
		    p->active = 1;
		  }
	      }
	  }
      }
    fclose(fp);
  }

  g_free (filename);

 /*
  * Select the current printer as necessary...
  */

  if (format == 1)
  {
    if (current_printer)
    {
      for (i = 0; i < plist_count; i ++)
        if (strcmp(current_printer, plist[i].name) == 0)
	  plist_current = i;
    }
  }
  else
  {
    if (vars.output_to[0] != '\0')
    {
      for (i = 0; i < plist_count; i ++)
        if (strcmp(vars.output_to, plist[i].v.output_to) == 0)
          break;

      if (i < plist_count)
        plist_current = i;
    }
  }
}


/*
 * 'printrc_save()' - Save the current printer resource configuration.
 */
void
printrc_save(void)
{
  FILE		*fp;		/* Printrc file */
  char	       *filename;	/* Printrc filename */
  int		i;		/* Looping var */
  plist_t	*p;		/* Current printer */
#if (GIMP_MINOR_VERSION == 0)
  char		*home;		/* Home dir */
#endif


 /*
  * Generate the filename for the current user...
  */

#if (GIMP_MINOR_VERSION == 0)
  home = getenv("HOME");
  if (home == NULL)
    filename=g_strdup("/.gimp/printrc");
  else
    filename = malloc(strlen(home) + 15);
    sprintf(filename, "%s/.gimp/printrc", home);
#else
  filename = gimp_personal_rc_file ("printrc");
#endif

#ifdef __EMX__
  _fnslashify(filename);
#endif

#ifndef __EMX__
  if ((fp = fopen(filename, "w")) != NULL)
#else
  if ((fp = fopen(filename, "wt")) != NULL)
#endif
  {
   /*
    * Write the contents of the printer list...
    */

#ifdef DEBUG
    fprintf(stderr, "Number of printers: %d\n", plist_count);
#endif

#if 0
    fputs("#PRINTRC " PLUG_IN_VERSION "\n", fp);

    for (i = 0, p = plist; i < plist_count; i ++, p ++)
      {
	fprintf(fp, "%s,%s,%s,%s,%d,%s,%s,%s,%s,",
		p->name, p->v.output_to, p->v.driver, p->v.ppd_file,
		p->v.output_type, p->v.resolution, p->v.media_size,
		p->v.media_type, p->v.media_source);
	fprintf(fp, "%.3f,%.3f,%d,%d,%d,%.3f,",
		p->v.brightness, p->v.scaling, p->v.orientation, p->v.left,
		p->v.top, p->v.gamma);
	fprintf(fp, "%.3f,%.3f,%.3f,%.3f,%d,%d,%.3f,%.3f,%s,%s,%d,\n",
		p->v.contrast, p->v.cyan, p->v.magenta, p->v.yellow,
		p->v.linear, p->v.image_type, p->v.saturation, p->v.density,
		p->v.ink_type, p->v.dither_algorithm, p->v.unit);

#ifdef DEBUG
        fprintf(stderr, "Wrote printer %d: %s\n", i, p->name);
#endif

      }
#else
    fputs("#PRINTRCv1 written by GIMP-PRINT " PLUG_IN_VERSION "\n", fp);

    fprintf(fp, "Current-Printer: %s\n", plist[plist_current].name);

    for (i = 0, p = plist; i < plist_count; i ++, p ++)
      {
	fprintf(fp, "\nPrinter: %s\n", p->name);
	fprintf(fp, "Destination: %s\n", p->v.output_to);
	fprintf(fp, "Driver: %s\n", p->v.driver);
	fprintf(fp, "PPD-File: %s\n", p->v.ppd_file);
	fprintf(fp, "Output-Type: %d\n", p->v.output_type);
	fprintf(fp, "Resolution: %s\n", p->v.resolution);
	fprintf(fp, "Media-Size: %s\n", p->v.media_size);
	fprintf(fp, "Media-Type: %s\n", p->v.media_type);
	fprintf(fp, "Media-Source: %s\n", p->v.media_source);
	fprintf(fp, "Brightness: %.3f\n", p->v.brightness);
	fprintf(fp, "Scaling: %.3f\n", p->v.scaling);
	fprintf(fp, "Orientation: %d\n", p->v.orientation);
	fprintf(fp, "Left: %d\n", p->v.left);
	fprintf(fp, "Top: %d\n", p->v.top);
	fprintf(fp, "Gamma: %.3f\n", p->v.gamma);
	fprintf(fp, "Contrast: %.3f\n", p->v.contrast);
	fprintf(fp, "Cyan: %.3f\n", p->v.cyan);
	fprintf(fp, "Magenta: %.3f\n", p->v.magenta);
	fprintf(fp, "Yellow: %.3f\n", p->v.yellow);
	fprintf(fp, "Linear: %d\n", p->v.linear);
	fprintf(fp, "Image-Type: %d\n", p->v.image_type);
	fprintf(fp, "Saturation: %.3f\n", p->v.saturation);
	fprintf(fp, "Density: %.3f\n", p->v.density);
	fprintf(fp, "Ink-Type: %s\n", p->v.ink_type);
	fprintf(fp, "Dither-Algorithm: %s\n", p->v.dither_algorithm);
	fprintf(fp, "Unit: %d\n", p->v.unit);

#ifdef DEBUG
        fprintf(stderr, "Wrote printer %d: %s\n", i, p->name);
#endif

      }
#endif
    fclose(fp);
  } else {
    fprintf(stderr,"could not open printrc file \"%s\"\n",filename);
  }
  g_free (filename);
}


/*
 * 'compare_printers()' - Compare system printer names for qsort().
 */

static int
compare_printers(plist_t *p1,	/* I - First printer to compare */
                 plist_t *p2)	/* I - Second printer to compare */
{
  return (strcmp(p1->name, p2->name));
}


/*
 * 'get_system_printers()' - Get a complete list of printers from the spooler.
 */

#define PRINTERS_NONE	0
#define PRINTERS_LPC	1
#define PRINTERS_LPSTAT	2

static void
get_system_printers(void)
{
  int   i;			/* Looping var */
  int	type;			/* 0 = none, 1 = lpc, 2 = lpstat */
  char	command[255];		/* Command to run */
  char  defname[128];		/* Default printer name */
  FILE *pfile;			/* Pipe to status command */
  char  line[255];		/* Line from status command */
  char	*ptr;			/* Pointer into line */
  char  name[128];		/* Printer name from status command */
#ifdef __EMX__
  BYTE  pnum;
#endif
  static char	*lpcs[] =	/* Possible locations of LPC... */
		{
		  "/etc"
		  "/usr/bsd",
		  "/usr/etc",
		  "/usr/libexec",
		  "/usr/sbin"
		};


 /*
  * Setup defaults...
  */

  defname[0] = '\0';

  check_plist(1);
  plist_count = 1;
  initialize_printer(&plist[0]);
  strcpy(plist[0].name, _("File"));
  plist[0].v.output_to[0] = '\0';
  strcpy(plist[0].v.driver, "ps2");
  plist[0].v.output_type = OUTPUT_COLOR;

 /*
  * Figure out what command to run...  We use lpstat if it is available over
  * lpc since Solaris, CUPS, etc. provide both commands.  No need to list
  * each printer twice...
  */

  if (!access("/usr/bin/lpstat", X_OK))
  {
    strcpy(command, "/usr/bin/lpstat -d -p");
    type = PRINTERS_LPSTAT;
  }
  else
  {
    for (i = 0; i < (sizeof(lpcs) / sizeof(lpcs[0])); i ++)
    {
      sprintf(command, "%s/lpc", lpcs[i]);

      if (!access(command, X_OK))
        break;
    }

    if (i < (sizeof(lpcs) / sizeof(lpcs[0])))
    {
      strcat(command, " status < /dev/null");
      type = PRINTERS_LPC;
    }
    else
      type = PRINTERS_NONE;
  }

 /*
  * Run the command, if any, to get the available printers...
  */

  if (type > PRINTERS_NONE)
  {
    if ((pfile = popen(command, "r")) != NULL)
    {
     /*
      * Read input as needed...
      */

      while (fgets(line, sizeof(line), pfile) != NULL)
        switch (type)
	{
	  case PRINTERS_LPC :
	      if (!strncmp(line, "Press RETURN to continue", 24) &&
		  (ptr = strchr(line, ':')) != NULL &&
		  (strlen(ptr) - 2) < (ptr - line))
		strcpy(line, ptr + 2);

	      if ((ptr = strchr(line, ':')) != NULL &&
	          line[0] != ' ' && line[0] != '\t')
              {
		check_plist(plist_count + 1);
		*ptr = '\0';
		initialize_printer(&plist[plist_count]);
		strncpy(plist[plist_count].name, line, sizeof(plist[0].name) - 1);
		plist[plist_count].name[sizeof(plist[0].name) - 1] = '\0';
		sprintf(plist[plist_count].v.output_to, "lpr -P%s -l", line);
		strcpy(plist[plist_count].v.driver, "ps2");
		plist_count ++;
	      }
	      break;

	  case PRINTERS_LPSTAT :
	      if ((sscanf(line, "printer %127s", name) == 1) ||
		  (sscanf(line, "Printer: %127s", name) == 1))
	      {
		check_plist(plist_count + 1);
		initialize_printer(&plist[plist_count]);
		strcpy(plist[plist_count].name, name);
		sprintf(plist[plist_count].v.output_to, "lp -s -d%s -oraw", name);
        	strcpy(plist[plist_count].v.driver, "ps2");
        	plist_count ++;
	      }
	      else
        	sscanf(line, "system default destination: %127s", defname);
	      break;
	}

      pclose(pfile);
    }
  }

#ifdef __EMX__
  if (DosDevConfig(&pnum, DEVINFO_PRINTER) == NO_ERROR)
    {
      for (i = 1; i <= pnum; i++)
	{
	  check_plist(plist_count + 1);
	  initialize_printer(&plist[plist_count]);
	  sprintf(plist[plist_count].name, "LPT%d:", i);
	  sprintf(plist[plist_count].v.output_to, "PRINT /D:LPT%d /B ", i);
          strcpy(plist[plist_count].v.driver, "ps2");
          plist_count ++;
	}
    }
#endif

  if (plist_count > 2)
    qsort(plist + 1, plist_count - 1, sizeof(plist_t),
          (int (*)(const void *, const void *))compare_printers);

  if (defname[0] != '\0' && vars.output_to[0] == '\0')
  {
    for (i = 0; i < plist_count; i ++)
      if (strcmp(defname, plist[i].name) == 0)
        break;

    if (i < plist_count)
      plist_current = i;
  }
}

/*
 * End of "$Id$".
 */
