/* xscanimage -- a graphical scanner-oriented SANE frontend

   Authors:
   Andreas Beck <becka@sunserver1.rz.uni-duesseldorf.de>
   Tristan Tarrant <ttarrant@etnoteam.it>
   David Mosberger-Tang <davidm@azstarnet.com>

   Copyright (C) 1997, 1998 Andreas Beck, Tristan Tarrant, and David
   Mosberger

   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., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#ifdef _AIX
# include <lalloca.h>	/* MUST come first for AIX! */
#endif

#include <sane/config.h>
#include <lalloca.h>

#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <gtkglue.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/stat.h>

#include <sane/sane.h>
#include <sane/sanei.h>
#include <sane/saneopts.h>

#include <progress.h>
#include <preferences.h>
#include <preview.h>

#ifndef PATH_MAX
# define PATH_MAX	1024
#endif

#define OUTFILENAME	"out.pnm"

#ifdef HAVE_LIBGIMP_GIMP_H

#include <libgimp/gimp.h>

static void query (void);
static void run (char *name, int nparams, GParam * param,
		 int *nreturn_vals, GParam ** return_vals);

GPlugInInfo PLUG_IN_INFO =
{
  NULL,				/* init_proc */
  NULL,				/* quit_proc */
  query,			/* query_proc */
  run,				/* run_proc */
};

#endif /* HAVE_LIBGIMP_GIMP_H */

enum
  {
    STANDALONE, SANE_GIMP_EXTENSION
  };

static struct
  {
    GtkWidget *shell;
    GtkWidget *hruler;
    GtkWidget *vruler;
    GtkWidget *info_label;
    Preview *preview;
    gint32 mode;
    /* various scanning related state: */
    size_t num_bytes;
    size_t bytes_read;
    Progress_t *progress;
    int input_tag;
    SANE_Parameters param;
    int x, y;
    /* for standalone mode: */
    GtkWidget *filename_entry;
    FILE *out;
    long header_size;
#ifdef HAVE_LIBGIMP_GIMP_H
    /* for GIMP mode: */
    gint32 image_ID;
    GDrawable *drawable;
    guchar *tile;
    unsigned tile_offset;
    GPixelRgn region;
    int first_frame;		/* used for RED/GREEN/BLUE frames */
#endif
  }
scan_win;

static const char *prog_name;
static GtkWidget *choose_device_dialog;
static GSGDialog *dialog;
static const SANE_Device **devlist;
static gint seldev = -1;	/* The selected device */
static gint ndevs;		/* The number of available devices */
static struct option long_options[] =
  {
    {"help", no_argument, NULL, 'h'},
    {"version", no_argument, NULL, 'V'},
    {0, }
  };


/* forward declarations: */

int main (int argc, char ** argv);
static void interface (int argc, char **argv);
static void scan_start (void);
static void scan_done (void);

#ifdef HAVE_LIBGIMP_GIMP_H

static int
encode_devname (const char *devname, int n, char *buf)
{
  static const char hexdigit[] = "0123456789abcdef";
  char *dst, *limit;
  const char *src;
  char ch;

  limit = buf + n;
  for (src = devname, dst = buf; *src; ++src)
    {
      if (dst >= limit)
	return -1;

      ch = *src;
      /* don't use the ctype.h macros here since we don't want to
	 allow anything non-ASCII here... */
      if ((ch >= '0' && ch <= '9')
	  || (ch >= 'a' && ch <= 'z')
	  || (ch >= 'A' && ch <= 'Z'))
	*dst++ = ch;
      else
	{
	  /* encode */

	  if (dst + 4 >= limit)
	    return -1;

	  *dst++ = '-';
	  if (ch == '-')
	    *dst++ = '-';
	  else
	    {
	      *dst++ = hexdigit[(ch >> 4) & 0x0f];
	      *dst++ = hexdigit[(ch >> 0) & 0x0f];
	      *dst++ = '-';
	    }
	}
    }
  if (dst >= limit)
    return -1;
  *dst = '\0';
  return 0;
}

static int
decode_devname (const char *encoded_devname, int n, char *buf)
{
  char *dst, *limit;
  const char *src;
  char ch, val;

  limit = buf + n;
  for (src = encoded_devname, dst = buf; *src; ++dst)
    {
      if (dst >= limit)
	return -1;

      ch = *src++;
      /* don't use the ctype.h macros here since we don't want to
	 allow anything non-ASCII here... */
      if (ch != '-')
	*dst = ch;
      else
	{
	  /* decode */

	  ch = *src++;
	  if (ch == '-')
	    *dst = ch;
	  else
	    {
	      if (ch >= 'a' && ch <= 'f')
		val = (ch - 'a') + 10;
	      else
		val = (ch - '0');
	      val <<= 4;

	      ch = *src++;
	      if (ch >= 'a' && ch <= 'f')
		val |= (ch - 'a') + 10;
	      else
		val |= (ch - '0');

	      *dst = val;

	      ++src;	/* simply skip terminating '-' for now... */
	    }
	}
    }
  if (dst >= limit)
    return -1;
  *dst = '\0';
  return 0;
}

static void
query (void)
{
  static GParamDef args[] =
    {
      {PARAM_INT32, "run_mode", "Interactive, non-interactive"},
    };
  static GParamDef *return_vals = NULL;
  static int nargs = sizeof (args) / sizeof (args[0]);
  static int nreturn_vals = 0;
  char mpath[1024];
  char name[1024];
  size_t len;
  int i, j;

  gimp_install_procedure (
      "xscanimage",
      "Front-end to the SANE interface",
      "This function provides access to scanners and other image acquisition "
      "devices through the SANE (Scanner Access Now Easy) interface.",
      "Andy Beck, Tristan Tarrant, and David Mosberger",
      "Andy Beck, Tristan Tarrant, and David Mosberger",
      "8th June 1997",
      "<Toolbox>/Xtns/Acquire Image/Device dialog...",
      "RGB, GRAY",
      PROC_EXTENSION,
      nargs, nreturn_vals,
      args, return_vals);

  sane_init (0, 0);
  sane_get_devices (&devlist, SANE_FALSE);

  for (i = 0; devlist[i]; ++i)
    {
      strcpy (name, "xscanimage-");
      if (encode_devname (devlist[i]->name, sizeof (name) - 11, name + 11) < 0)
	continue;	/* name too long... */

      strncpy (mpath, "<Toolbox>/Xtns/Acquire Image/", sizeof (mpath));
      len = strlen (mpath);
      for (j = 0; devlist[i]->name[j]; ++j)
	{
	  if (devlist[i]->name[j] == '/')
	    mpath[len++] = '\'';
	  else
	    mpath[len++] = devlist[i]->name[j];
	}
      mpath[len++] = '\0';

      gimp_install_procedure
	(name, "Front-end to the SANE interface",
	 "This function provides access to scanners and other image "
	 "acquisition devices through the SANE (Scanner Access Now Easy) "
	 "interface.",
	 "Andy Beck, Tristan Tarrant, and David Mosberger",
	 "Andy Beck, Tristan Tarrant, and David Mosberger",
	 "8th June 1997", mpath, "RGB, GRAY", PROC_EXTENSION,
	 nargs, nreturn_vals, args, return_vals);
    }
  sane_exit ();
}

static void
run (char *name, int nparams, GParam * param,
     int *nreturn_vals, GParam ** return_vals)
{
  static GParam values[2];
  GRunModeType run_mode;
  char devname[1024];
  char *args[2];
  int nargs;

  run_mode = param[0].data.d_int32;
  scan_win.mode = SANE_GIMP_EXTENSION;

  *nreturn_vals = 1;
  *return_vals = values;

  values[0].type = PARAM_STATUS;
  values[0].data.d_status = STATUS_CALLING_ERROR;

  nargs = 0;
  args[nargs++] = "xscanimage";

  seldev = -1;
  if (strncmp (name, "xscanimage-", 11) == 0)
    {
      if (decode_devname (name + 11, sizeof (devname), devname) < 0)
	return;			/* name too long */
      args[nargs++] = devname;
    }

  switch (run_mode)
    {
    case RUN_INTERACTIVE:
      interface (nargs, args);
      values[0].data.d_status = STATUS_SUCCESS;
      break;

    case RUN_NONINTERACTIVE:
      /*  Make sure all the arguments are there!  */
      break;

    case RUN_WITH_LAST_VALS:
      /*  Possibly retrieve data  */
      break;

    default:
      break;
    }
}

static void
null_print_func (const gchar *msg)
{
}

#endif /* HAVE_LIBGIMP_GIMP_H */

static void
update_preview (GSGDialog *dialog, void *arg)
{
  if (scan_win.preview)
    preview_update (scan_win.preview);
}

/* Update the info line with the latest size information.  */
static void
update_param (GSGDialog *dialog, void *arg)
{
  SANE_Parameters params;
  gchar buf[200];

  if (!scan_win.info_label)
    return;

  if (sane_get_parameters (gsg_dialog_get_device (dialog), &params)
      == SANE_STATUS_GOOD)
    {
      u_long size = 10 * params.bytes_per_line * params.lines;
      const char *unit = "B";

      if (params.format >= SANE_FRAME_RED && params.format <= SANE_FRAME_BLUE)
	size *= 3;

      if (size >= 10 * 1024 * 1024)
	{
	  size /= 1024 * 1024;
	  unit = "MB";
	}
      else if (size >= 10 * 1024)
	{
	  size /= 1024;
	  unit = "KB";
	}
      sprintf (buf, "%dx%d: %lu.%01lu %s", params.pixels_per_line,
	       params.lines, size / 10, size % 10, unit);
    }
  else
    sprintf (buf, "Invalid parameters.");
  gtk_label_set (GTK_LABEL (scan_win.info_label), buf);

  if (scan_win.preview)
    preview_update (scan_win.preview);
}

static void
pref_xscan_save (void)
{
  char filename[PATH_MAX];
  int fd;

  /* first save xscanimage-specific preferences: */
  gsg_make_path (sizeof (filename), filename, "xscanimage", "xscanimage",
		 0, ".rc");
  fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
  if (fd < 0)
    {
      char buf[256];

      snprintf (buf, sizeof (buf), "Failed to create file: %s.",
		strerror (errno));
      gsg_error (buf);
      return;
    }
  preferences_save (fd);
  close (fd);
}

static void
pref_xscan_restore (void)
{
  char filename[PATH_MAX];
  int fd;

  gsg_make_path (sizeof (filename), filename, "xscanimage", "xscanimage",
		 0, ".rc");
  fd = open (filename, O_RDONLY);
  if (fd >= 0)
    {
      preferences_restore (fd);
      close (fd);
    }
  if (!preferences.filename)
    preferences.filename = strdup (OUTFILENAME);
}

static void
quit_xscan (void)
{
  if (scan_win.preview)
    {
      Preview *preview = scan_win.preview;
      scan_win.preview = 0;
      preview_destroy (preview);
    }
  while (gsg_message_dialog_active)
    {
      if (!gtk_events_pending ())
	usleep (100000);
      gtk_main_iteration ();
    }
  pref_xscan_save ();
  if (dialog && gsg_dialog_get_device (dialog))
    sane_close (gsg_dialog_get_device (dialog));
  sane_exit ();
  gtk_main_quit ();
#ifdef HAVE_LIBGIMP_GIMP_H
  if (scan_win.mode == SANE_GIMP_EXTENSION)
    gimp_quit ();
#endif
  exit (0);
}

/* Invoked when window manager's "delete" (or "close") function is
   invoked.  */
static gint
scan_win_delete (GtkWidget *w, gpointer data)
{
  quit_xscan();
  return FALSE;
}

static void
preview_window_destroyed (GtkWidget *widget, gpointer call_data)
{
  gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (call_data), FALSE);
}

/* Invoked when the preview button is pressed. */

static void
scan_preview (GtkWidget * widget, gpointer call_data)
{
  if (GTK_TOGGLE_BUTTON (widget)->active)
    {
      if (!scan_win.preview)
	{
	  scan_win.preview = preview_new (dialog);
	  if (scan_win.preview && scan_win.preview->top)
	    gtk_signal_connect (GTK_OBJECT (scan_win.preview->top),
				"destroy",
				GTK_SIGNAL_FUNC (preview_window_destroyed),
				widget);
	  else
	    gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (widget), FALSE);
	}
    }
  else if (scan_win.preview)
    {
      preview_destroy (scan_win.preview);
      scan_win.preview = 0;
    }
}

#ifdef HAVE_LIBGIMP_GIMP_H
static void
advance (void)
{
  if (++scan_win.x >= scan_win.param.pixels_per_line)
    {
      int tile_height = gimp_tile_height ();

      scan_win.x = 0;
      ++scan_win.y;
      if (scan_win.y % tile_height == 0)
	{
	  gimp_pixel_rgn_set_rect (&scan_win.region, scan_win.tile,
				   0, scan_win.y - tile_height,
				   scan_win.param.pixels_per_line, tile_height);
	  if (scan_win.param.format >= SANE_FRAME_RED
	      && scan_win.param.format <= SANE_FRAME_BLUE)
	    {
	      int height;

	      scan_win.tile_offset %= 3;
	      if (!scan_win.first_frame)
		{
		  /* get the data for the existing tile: */
		  height = tile_height;
		  if (scan_win.y + height >= scan_win.param.lines)
		    height = scan_win.param.lines - scan_win.y;
		  gimp_pixel_rgn_get_rect (&scan_win.region, scan_win.tile,
					   0, scan_win.y,
					   scan_win.param.pixels_per_line,
					   height);
		}
	    }
	  else
	    scan_win.tile_offset = 0;
	}
    }
}

#endif /* HAVE_LIBGIMP_GIMP_H */

static void
input_available (gpointer data, gint source, GdkInputCondition cond)
{
  SANE_Handle dev = gsg_dialog_get_device (dialog);
  static char buf[32768];
  SANE_Status status;
  SANE_Int len;
  int i;

  while (1)
    {
      status = sane_read (dev, buf, sizeof (buf), &len);
      if (status != SANE_STATUS_GOOD)
	{
	  if (status == SANE_STATUS_EOF)
	    {
	      if (!scan_win.param.last_frame)
		{
		  scan_start ();
		  break;
		}
	    }
	  else
	    {
	      snprintf (buf, sizeof (buf), "Error during read: %s.",
			sane_strstatus (status));
	      gsg_error (buf);
	    }
	  scan_done ();
	  return;
	}
      if (!len)
	break;			/* out of data for now */
      scan_win.bytes_read += len;
      progress_update (scan_win.progress,
		       scan_win.bytes_read / (gfloat) scan_win.num_bytes);
      if (scan_win.input_tag < 0)
	while (gtk_events_pending ())
	  gtk_main_iteration ();

      switch (scan_win.param.format)
	{
	case SANE_FRAME_GRAY:
	  if (scan_win.mode == STANDALONE)
	    fwrite (buf, 1, len, scan_win.out);
#ifdef HAVE_LIBGIMP_GIMP_H
	  else
	    {
	      switch (scan_win.param.depth)
		{
		case 1:
		  for (i = 0; i < len; ++i)
		    {
		      u_char mask;
		      int j;

		      mask = buf[i];
		      for (j = 7; j >= 0; --j)
			{
			  u_char gl = (mask & (1 << j)) ? 0x00 : 0xff;
			  scan_win.tile[scan_win.tile_offset++] = gl;
			  advance ();
			  if (scan_win.x == 0)
			    break;
			}
		    }
		  break;

		case 8:
		  for (i = 0; i < len; ++i)
		    {
		      scan_win.tile[scan_win.tile_offset++] = buf[i];
		      advance ();
		    }
		  break;

		default:
		  goto bad_depth;
		}
	    }
#endif /* HAVE_LIBGIMP_GIMP_H */
	  break;

	case SANE_FRAME_RGB:
	  if (scan_win.mode == STANDALONE)
	    fwrite (buf, 1, len, scan_win.out);
#ifdef HAVE_LIBGIMP_GIMP_H
	  else
	    {
	      switch (scan_win.param.depth)
		{
		case 1:
		  if (scan_win.param.format == SANE_FRAME_RGB)
		    goto bad_depth;
		  for (i = 0; i < len; ++i)
		    {
		      u_char mask;
		      int j;

		      mask = buf[i];
		      for (j = 0; j < 8; ++j)
			{
			  u_char gl = (mask & 1) ? 0xff : 0x00;
			  mask >>= 1;
			  scan_win.tile[scan_win.tile_offset++] = gl;
			  advance ();
			  if (scan_win.x == 0)
			    break;
			}
		    }
		  break;

		case 8:
		  for (i = 0; i < len; ++i)
		    {
		      scan_win.tile[scan_win.tile_offset++] = buf[i];
		      if (scan_win.tile_offset % 3 == 0)
			advance ();
		    }
		  break;

		default:
		  goto bad_depth;
		}
	    }
#endif /* HAVE_LIBGIMP_GIMP_H */
	  break;

	case SANE_FRAME_RED:
	case SANE_FRAME_GREEN:
	case SANE_FRAME_BLUE:
	  if (scan_win.mode == STANDALONE)
	    for (i = 0; i < len; ++i)
	      {
		fwrite (buf + i, 1, 1, scan_win.out);
		fseek (scan_win.out, 2, SEEK_CUR);
	      }
#ifdef HAVE_LIBGIMP_GIMP_H
	  else
	    {
	      switch (scan_win.param.depth)
		{
		case 1:
		  for (i = 0; i < len; ++i)
		    {
		      u_char mask;
		      int j;

		      mask = buf[i];
		      for (j = 0; j < 8; ++j)
			{
			  u_char gl = (mask & 1) ? 0xff : 0x00;
			  mask >>= 1;
			  scan_win.tile[scan_win.tile_offset] = gl;
			  scan_win.tile_offset += 3;
			  advance ();
			  if (scan_win.x == 0)
			    break;
			}
		    }
		  break;

		case 8:
		  for (i = 0; i < len; ++i)
		    {
		      scan_win.tile[scan_win.tile_offset] = buf[i];
		      scan_win.tile_offset += 3;
		      advance ();
		    }
		  break;
		}
	    }
#endif /* HAVE_LIBGIMP_GIMP_H */
	  break;

	default:
	  fprintf (stderr, "%s.input_available: bad frame format %d\n",
		   prog_name, scan_win.param.format);
	  scan_done ();
	  return;
	}
    }
  return;

#ifdef HAVE_LIBGIMP_GIMP_H
bad_depth:
  snprintf (buf, sizeof (buf), "Cannot handle depth %d.",
	    scan_win.param.depth);
  gsg_error (buf);
  scan_done ();
  return;
#endif
}

static void
scan_done (void)
{
  gsg_set_sensitivity (dialog, TRUE);

  if (scan_win.input_tag >= 0)
    {
      gdk_input_remove (scan_win.input_tag);
      scan_win.input_tag = -1;
    }

  if (!scan_win.progress)
    return;

  progress_free (scan_win.progress);
  scan_win.progress = 0;
  scan_win.header_size = 0;

  if (scan_win.mode == STANDALONE)
    {
      fclose (scan_win.out);
      scan_win.out = 0;
    }
#ifdef HAVE_LIBGIMP_GIMP_H
  else
    {
      int remaining;

      /* GIMP mode */
      if (scan_win.y > scan_win.param.lines)
	scan_win.y = scan_win.param.lines;

      remaining = scan_win.y % gimp_tile_height ();
      if (remaining)
	gimp_pixel_rgn_set_rect (&scan_win.region, scan_win.tile,
				 0, scan_win.y - remaining,
				 scan_win.param.pixels_per_line, remaining);
      gimp_drawable_flush (scan_win.drawable);
      gimp_display_new (scan_win.image_ID);
      gimp_drawable_detach (scan_win.drawable);
      free (scan_win.tile);
      scan_win.tile = 0;
    }
#endif /* HAVE_LIBGIMP_GIMP_H */
  sane_cancel (gsg_dialog_get_device (dialog));
}

static void
progress_cancel (void)
{
  sane_cancel (gsg_dialog_get_device (dialog));
}

static void
scan_start (void)
{
  SANE_Status status;
  SANE_Handle dev = gsg_dialog_get_device (dialog);
  const char *frame_type = 0;
  char buf[256];
  int fd;

  gsg_set_sensitivity (dialog, FALSE);

#ifdef HAVE_LIBGIMP_GIMP_H
  if (scan_win.mode == SANE_GIMP_EXTENSION && scan_win.tile)
    {
      int height, remaining;

      /* write the last tile of the frame to the GIMP region: */

      if (scan_win.y > scan_win.param.lines)	/* sanity check */
	scan_win.y = scan_win.param.lines;

      remaining = scan_win.y % gimp_tile_height ();
      if (remaining)
	gimp_pixel_rgn_set_rect (&scan_win.region, scan_win.tile,
				 0, scan_win.y - remaining,
				 scan_win.param.pixels_per_line, remaining);

      /* initialize the tile with the first tile of the GIMP region: */

      height = gimp_tile_height ();
      if (height >= scan_win.param.lines)
	height = scan_win.param.lines;
      gimp_pixel_rgn_get_rect (&scan_win.region, scan_win.tile,
			       0, 0, scan_win.param.pixels_per_line, height);
    }
#endif /* HAVE_LIBGIMP_GIMP_H */

  scan_win.x = scan_win.y = 0;

  status = sane_start (dev);
  if (status != SANE_STATUS_GOOD)
    {
      gsg_set_sensitivity (dialog, TRUE);
      snprintf (buf, sizeof (buf), "Failed to start scanner: %s",
		sane_strstatus (status));
      gsg_error (buf);
      return;
    }

  status = sane_get_parameters (dev, &scan_win.param);
  if (status != SANE_STATUS_GOOD)
    {
      gsg_set_sensitivity (dialog, TRUE);
      snprintf (buf, sizeof (buf), "Failed to get parameters: %s",
		sane_strstatus (status));
      gsg_error (buf);
      return;
    }

  scan_win.num_bytes = scan_win.param.lines * scan_win.param.bytes_per_line;
  scan_win.bytes_read = 0;

  switch (scan_win.param.format)
    {
    case SANE_FRAME_RGB:	frame_type = "RGB"; break;
    case SANE_FRAME_RED:	frame_type = "red"; break;
    case SANE_FRAME_GREEN:	frame_type = "green"; break;
    case SANE_FRAME_BLUE:	frame_type = "blue"; break;
    case SANE_FRAME_GRAY:	frame_type = "gray"; break;
    }

  if (scan_win.mode == STANDALONE)
    {				/* We are running in standalone mode */
      if (!scan_win.header_size)
	{
	  switch (scan_win.param.format)
	    {
	    case SANE_FRAME_RGB:
	    case SANE_FRAME_RED:
	    case SANE_FRAME_GREEN:
	    case SANE_FRAME_BLUE:
	      fprintf (scan_win.out, "P6\n# SANE data follows\n%d %d\n255\n",
		       scan_win.param.pixels_per_line, scan_win.param.lines);
	      break;

	    case SANE_FRAME_GRAY:
	      if (scan_win.param.depth == 1)
		fprintf (scan_win.out, "P4\n# SANE data follows\n%d %d\n",
			 scan_win.param.pixels_per_line, scan_win.param.lines);
	      else
		fprintf (scan_win.out, "P5\n# SANE data follows\n%d %d\n255\n",
			 scan_win.param.pixels_per_line, scan_win.param.lines);
	      break;
	    }
	  scan_win.header_size = ftell (scan_win.out);
	}
      if (scan_win.param.format >= SANE_FRAME_RED
	  && scan_win.param.format <= SANE_FRAME_BLUE)
	fseek (scan_win.out,
	       scan_win.header_size + scan_win.param.format - SANE_FRAME_RED,
	       SEEK_SET);
      snprintf (buf, sizeof (buf), "Receiving %s data for `%s'...",
		frame_type, preferences.filename);
    }
#ifdef HAVE_LIBGIMP_GIMP_H
  else
    {
      size_t tile_size;

      /* We are running under the GIMP */

      scan_win.tile_offset = 0;
      tile_size = scan_win.param.pixels_per_line * gimp_tile_height ();
      if (scan_win.param.format != SANE_FRAME_GRAY)
	tile_size *= 3;	/* 24 bits/pixel */
      if (scan_win.tile)
	scan_win.first_frame = 0;
      else
	{
	  GImageType image_type = RGB;
	  GDrawableType drawable_type = RGB_IMAGE;
	  gint32 layer_ID;

	  if (scan_win.param.format == SANE_FRAME_GRAY)
	    {
	      image_type = GRAY;
	      drawable_type = GRAY_IMAGE;
	    }

	  scan_win.image_ID = gimp_image_new (scan_win.param.pixels_per_line,
					     scan_win.param.lines, image_type);
	  layer_ID = gimp_layer_new (scan_win.image_ID, "Background",
				     scan_win.param.pixels_per_line,
				     scan_win.param.lines,
				     drawable_type, 100, NORMAL_MODE);
	  gimp_image_add_layer (scan_win.image_ID, layer_ID, 0);

	  scan_win.drawable = gimp_drawable_get (layer_ID);
	  gimp_pixel_rgn_init (&scan_win.region, scan_win.drawable, 0, 0,
			       scan_win.drawable->width,
			       scan_win.drawable->height, TRUE, FALSE);
	  scan_win.tile = g_new (guchar, tile_size);
	  scan_win.first_frame = 1;
	}
      if (scan_win.param.format >= SANE_FRAME_RED
	  && scan_win.param.format <= SANE_FRAME_BLUE)
	scan_win.tile_offset = scan_win.param.format - SANE_FRAME_RED;
      snprintf (buf, sizeof (buf), "Receiving %s data for GIMP...",
		frame_type);
    }
#endif /* HAVE_LIBGIMP_GIMP_H */
  if (scan_win.progress)
    progress_free (scan_win.progress);
  scan_win.progress = progress_new ("Scanning", buf,
				    (GtkSignalFunc) progress_cancel, 0);

  scan_win.input_tag = -1;
  if (sane_set_io_mode (dev, SANE_TRUE) == SANE_STATUS_GOOD
      && sane_get_select_fd (dev, &fd) == SANE_STATUS_GOOD)
    scan_win.input_tag = gdk_input_add (fd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION,
					input_available, 0);
  else
    input_available (0, -1, GDK_INPUT_READ);
}

/* Invoked when the scan button is pressed */
static void
scan_dialog (GtkWidget * widget, gpointer call_data)
{
  char buf[256];
  
  if (scan_win.mode == STANDALONE)
    {				/* We are running in standalone mode */
      scan_win.out = fopen (preferences.filename, "w");
      if (!scan_win.out)
	{
	  snprintf (buf, sizeof (buf), "Failed to open `%s': %s",
		    preferences.filename, strerror (errno));
	  gsg_error (buf);
	  return;
	}
    }
  gsg_sync (dialog);
  scan_start ();
}

#if 0

static void
zoom_in_preview (GtkWidget * widget, gpointer data)
{
  if (Selection.x1 >= Selection.x2
      || Selection.y1 >= Selection.y2
      || !Selection.active)
    return;

  Selection.active = FALSE;
  draw_selection (TRUE);

  gtk_ruler_set_range (GTK_RULER (scan_win.hruler), Selection.x1,
		       Selection.x2, 0, 20);
  gtk_ruler_set_range (GTK_RULER (scan_win.vruler), Selection.y1,
		       Selection.y2, 0, 20);
}

static void
zoom_out_preview (GtkWidget * widget, gpointer data)
{
  gtk_ruler_set_range (GTK_RULER (scan_win.hruler), 0,
		       Preview.PhysWidth, 0, 20);
  gtk_ruler_set_range (GTK_RULER (scan_win.vruler), 0,
		       Preview.PhysHeight, 0, 20);
}

#endif /* 0 */

static void
files_exit_callback (GtkWidget *widget, gpointer data)
{
  quit_xscan ();
}

static GtkWidget *
files_build_menu (void)
{
  GtkWidget *menu, *item;

  menu = gtk_menu_new ();

  item = gtk_menu_item_new ();
  gtk_container_add (GTK_CONTAINER (menu), item);
  gtk_widget_show (item);

  item = gtk_menu_item_new_with_label ("Exit");
  gtk_container_add (GTK_CONTAINER (menu), item);
  gtk_signal_connect (GTK_OBJECT (item), "activate",
		      (GtkSignalFunc) files_exit_callback, 0);
  gtk_widget_show (item);

  return menu;
}

static void
pref_set_unit_callback (GtkWidget *widget, gpointer data)
{
  const char *unit = data;
  double unit_conversion_factor = 1.0;

  if (strcmp (unit, "cm") == 0)
    unit_conversion_factor = 10.0;
  else if (strcmp (unit, "in") == 0)
    unit_conversion_factor = 25.4;

  preferences.length_unit = unit_conversion_factor;

  gsg_refresh_dialog (dialog);
  if (scan_win.preview)
    preview_update (scan_win.preview);

  pref_xscan_save ();
}

static void
update_int_callback (GtkWidget *widget, gpointer data)
{
  int *valuep = data;

  *valuep = (GTK_TOGGLE_BUTTON (widget)->active != 0);
}

static void
update_double_callback (GtkWidget *widget, gpointer data)
{
  double *valuep = data;
  char *start, *end;
  double v;

  start = gtk_entry_get_text (GTK_ENTRY (widget));
  if (!start)
    return;

  v = strtod (start, &end);
  if (end > start && v > 0.0)
    *valuep = v;
}

static void
preview_options_ok_callback (GtkWidget *widget, gpointer data)
{
  GtkWidget *dialog = data;
  char buf[1024];

  gtk_widget_destroy (dialog);
  pref_xscan_save ();

  snprintf (buf, sizeof (buf),
	    "It is necessary to restart %s for the changes to take effect.",
	    prog_name);
  gsg_warning (buf);
}

static void
preview_options_cancel_callback (GtkWidget *widget, gpointer data)
{
  GtkWidget *dialog = data;

  gtk_widget_destroy (dialog);
}

static void
preview_options_dialog (GtkWidget *widget, gpointer data)
{
  GtkWidget *dialog, *vbox, *hbox, *button, *label, *text;
  char buf[64];

  dialog = gtk_dialog_new ();
  sprintf (buf, "%s preview options", prog_name);
  gtk_window_set_title (GTK_WINDOW (dialog), buf);

  vbox = GTK_DIALOG (dialog)->vbox;

  /* preserve preview image: */

  hbox = gtk_hbox_new (/* homogeneous */ FALSE, 0);
  gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 2);
  button = gtk_check_button_new_with_label ("Preserve preview image");
  gtk_signal_connect (GTK_OBJECT (button), "toggled",
		      (GtkSignalFunc) update_int_callback,
		      &preferences.preserve_preview);
  gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (button),
			       preferences.preserve_preview);
  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 2);

  gtk_widget_show (button);
  gtk_widget_show (hbox);

  /* private colormap: */

  hbox = gtk_hbox_new (/* homogeneous */ FALSE, 0);
  gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 2);
  button = gtk_check_button_new_with_label ("Use private colormap");
  gtk_signal_connect (GTK_OBJECT (button), "toggled",
		      (GtkSignalFunc) update_int_callback,
		      &preferences.preview_own_cmap);
  gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (button),
			       preferences.preview_own_cmap);
  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 2);

  gtk_widget_show (button);
  gtk_widget_show (hbox);

  /* gamma correction value: */

  hbox = gtk_hbox_new (/* homogeneous */ FALSE, 0);
  gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 2);
  gtk_widget_show (hbox);

  label = gtk_label_new ("Gamma correction value");
  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 2);
  gtk_widget_show (label);

  sprintf (buf, "%g", preferences.preview_gamma);
  text = gtk_entry_new ();
  gtk_entry_set_text (GTK_ENTRY (text), buf);
  gtk_box_pack_start (GTK_BOX (hbox), text, TRUE, TRUE, 2);
  gtk_signal_connect (GTK_OBJECT (text), "changed",
		      (GtkSignalFunc) update_double_callback,
		      &preferences.preview_gamma);
  gtk_widget_show (text);

  /* fill in action area: */
  hbox = GTK_DIALOG (dialog)->action_area;

  button = gtk_button_new_with_label ("OK");
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      (GtkSignalFunc) preview_options_ok_callback, dialog);
  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
  gtk_widget_grab_default (button);
  gtk_widget_show (button);

  button = gtk_button_new_with_label ("Cancel");
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      (GtkSignalFunc) preview_options_cancel_callback, dialog);
  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
  gtk_widget_show (button);

  gtk_widget_show (dialog);
}

static void
pref_device_save (GtkWidget *widget, gpointer data)
{
  char filename[PATH_MAX];
  int fd;

  gsg_make_path (sizeof (filename), filename,
		 "xscanimage", 0, dialog->dev_name, ".rc");
  fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
  if (fd < 0)
    {
      char buf[256];

      snprintf (buf, sizeof (buf), "Failed to create file: %s.",
		strerror (errno));
      gsg_error (buf);
      return;
    }
  gsg_sync (dialog);
  sanei_save_values (fd, dialog->dev);
  close (fd);
}

static void
pref_device_restore (void)
{
  char filename[PATH_MAX];
  int fd;

  gsg_make_path (sizeof (filename), filename,
		 "xscanimage", 0, dialog->dev_name, ".rc");
  fd = open (filename, O_RDONLY);
  if (fd < 0)
    return;
  sanei_load_values (fd, dialog->dev);
  close (fd);

  gsg_refresh_dialog (dialog);
}

static void
pref_toggle_advanced (GtkWidget *widget, gpointer data)
{
  preferences.advanced = (GTK_CHECK_MENU_ITEM (widget)->active != 0);
  gsg_set_advanced (dialog, preferences.advanced);
  pref_xscan_save ();
}

static void
pref_toggle_tooltips (GtkWidget *widget, gpointer data)
{
  preferences.tooltips_enabled = (GTK_CHECK_MENU_ITEM (widget)->active != 0);
  gsg_set_tooltips (dialog, preferences.tooltips_enabled);
  pref_xscan_save ();
}

static GtkWidget *
pref_build_menu (void)
{
  GtkWidget *menu, *item, *submenu, *subitem;

  menu = gtk_menu_new ();

  /* advanced user option: */
  item = gtk_check_menu_item_new_with_label ("Show advanced options");
  gtk_check_menu_item_set_state (GTK_CHECK_MENU_ITEM (item),
				 preferences.advanced);
  gtk_menu_append (GTK_MENU (menu), item);
  gtk_widget_show (item);
  gtk_signal_connect (GTK_OBJECT (item), "toggled",
		      (GtkSignalFunc) pref_toggle_advanced, 0);

  /* tooltips submenu: */

  item = gtk_check_menu_item_new_with_label ("Show tooltips");
  gtk_check_menu_item_set_state (GTK_CHECK_MENU_ITEM (item),
				 preferences.tooltips_enabled);
  gtk_menu_append (GTK_MENU (menu), item);
  gtk_widget_show (item);
  gtk_signal_connect (GTK_OBJECT (item), "toggled",
		      (GtkSignalFunc) pref_toggle_tooltips, 0);

  /* length unit submenu: */

  item = gtk_menu_item_new_with_label ("Length unit");
  gtk_menu_append (GTK_MENU (menu), item);
  gtk_widget_show (item);

  submenu = gtk_menu_new ();

  subitem = gtk_menu_item_new_with_label ("millimeters");
  gtk_menu_append (GTK_MENU (submenu), subitem);
  gtk_signal_connect (GTK_OBJECT (subitem), "activate",
		      (GtkSignalFunc) pref_set_unit_callback, "mm");
  gtk_widget_show (subitem);

  subitem = gtk_menu_item_new_with_label ("centimeters");
  gtk_menu_append (GTK_MENU (submenu), subitem);
  gtk_signal_connect (GTK_OBJECT (subitem), "activate",
		      (GtkSignalFunc) pref_set_unit_callback, "cm");
  gtk_widget_show (subitem);

  subitem = gtk_menu_item_new_with_label ("inches");
  gtk_menu_append (GTK_MENU (submenu), subitem);
  gtk_signal_connect (GTK_OBJECT (subitem), "activate",
		      (GtkSignalFunc) pref_set_unit_callback, "in");
  gtk_widget_show (subitem);

  gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);

  /* preview options: */

  item = gtk_menu_item_new_with_label ("Preview options...");
  gtk_menu_append (GTK_MENU (menu), item);
  gtk_signal_connect (GTK_OBJECT (item), "activate",
		      (GtkSignalFunc) preview_options_dialog, 0);
  gtk_widget_show (item);

  /* insert separator: */
  item = gtk_menu_item_new ();
  gtk_menu_append (GTK_MENU (menu), item);
  gtk_widget_show (item);

  item = gtk_menu_item_new_with_label ("Save device settings");
  gtk_menu_append (GTK_MENU (menu), item);
  gtk_signal_connect (GTK_OBJECT (item), "activate",
		      (GtkSignalFunc) pref_device_save, 0);
  gtk_widget_show (item);

  item = gtk_menu_item_new_with_label ("Restore device settings");
  gtk_menu_append (GTK_MENU (menu), item);
  gtk_signal_connect (GTK_OBJECT (item), "activate",
		      (GtkSignalFunc) pref_device_restore, 0);
  gtk_widget_show (item);

  return menu;
}

static void
browse_filename_callback (GtkWidget *widget, gpointer data)
{
  char filename[1024];

  if (preferences.filename)
    {
      strncpy (filename, preferences.filename, sizeof (filename));
      filename[sizeof (filename) - 1] = '\0';
    }
  else
    strcpy (filename, OUTFILENAME);
  gsg_get_filename ("Output Filename", filename, sizeof (filename), filename);
  gtk_entry_set_text (GTK_ENTRY (scan_win.filename_entry), filename);

  if (preferences.filename)
    free ((void *) preferences.filename);
  preferences.filename = strdup (filename);
}

static void
filename_changed_callback (GtkWidget *widget, gpointer data)
{
  if (preferences.filename)
    free ((void *) preferences.filename);
  preferences.filename = strdup (gtk_entry_get_text (GTK_ENTRY (widget)));
}

/* Create the main dialog box.  */
static void
device_dialog (void)
{
  GtkWidget *vbox, *hbox, *button, *frame, *dialog_window, *label, *text;
  GtkWidget *menubar, *menubar_item;
  const gchar *devname;

  /* first, restore xscan preferences */
  pref_xscan_restore ();

  devname = devlist[seldev]->name;

  /* create the dialog box */
  scan_win.shell = gtk_dialog_new ();
  gtk_window_set_title (GTK_WINDOW (scan_win.shell), (char *) devname);
  gtk_window_set_policy (GTK_WINDOW (scan_win.shell), FALSE, TRUE, TRUE);
  gtk_signal_connect (GTK_OBJECT (scan_win.shell), "delete_event",
		      GTK_SIGNAL_FUNC (scan_win_delete), NULL);

  /* create the main vbox */
  vbox = GTK_DIALOG (scan_win.shell)->vbox;

  /* create the menubar */

  menubar = gtk_menu_bar_new ();
  gtk_box_pack_start (GTK_BOX (vbox), menubar, FALSE, FALSE, 0);

  /* "Files" submenu: */
  menubar_item = gtk_menu_item_new_with_label ("File");
  gtk_container_add (GTK_CONTAINER (menubar), menubar_item);
  gtk_menu_item_set_submenu (GTK_MENU_ITEM (menubar_item),
			     files_build_menu ());
  gtk_widget_show (menubar_item);

  /* "Preferences" submenu: */
  menubar_item = gtk_menu_item_new_with_label ("Preferences");
  gtk_container_add (GTK_CONTAINER (menubar), menubar_item);
  gtk_menu_item_set_submenu (GTK_MENU_ITEM (menubar_item),
			     pref_build_menu ());
  gtk_widget_show (menubar_item);

  gtk_widget_show (menubar);

  /* if we're running in standalone mode, provide a output filename box: */
  if (scan_win.mode == STANDALONE)
    {
      frame = gtk_frame_new ("Output");
      gtk_container_border_width (GTK_CONTAINER (frame), 4);
      gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
      gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);

      hbox = gtk_hbox_new (FALSE, 2);
      gtk_container_border_width (GTK_CONTAINER (hbox), 2);
      gtk_container_add (GTK_CONTAINER (frame), hbox);

      label = gtk_label_new ("Filename");
      gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 2);

      text = gtk_entry_new ();
      gtk_entry_set_text (GTK_ENTRY (text), (char *) preferences.filename);
      gtk_box_pack_start (GTK_BOX (hbox), text, TRUE, TRUE, 2);
      gtk_signal_connect (GTK_OBJECT (text), "changed",
			  (GtkSignalFunc) filename_changed_callback, 0);

      scan_win.filename_entry = text;

      button = gtk_button_new_with_label ("Browse");
      gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 2);
      gtk_signal_connect (GTK_OBJECT (button), "clicked",
			  (GtkSignalFunc) browse_filename_callback, 0);

      gtk_widget_show (button);
      gtk_widget_show (label);
      gtk_widget_show (text);
      gtk_widget_show (hbox);
      gtk_widget_show (frame);
    }

  /* create  a subwindow so the dialog keeps its position on rebuilds: */
  dialog_window = gtk_hbox_new (/* homogeneous */ FALSE, 0);
  gtk_box_pack_start (GTK_BOX (vbox), dialog_window, TRUE, TRUE, 0);
  gtk_widget_show (dialog_window);

  dialog = gsg_create_dialog (dialog_window, devname,
			      update_preview, 0, update_param, 0);
  if (!dialog)
    return;

  /* The info row */
  hbox = gtk_hbox_new (FALSE, 5);
  gtk_container_border_width (GTK_CONTAINER (hbox), 3);
  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
  gtk_widget_show (hbox);

  frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
  gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  hbox = gtk_hbox_new (FALSE, 5);
  gtk_container_border_width (GTK_CONTAINER (hbox), 2);
  gtk_container_add (GTK_CONTAINER (frame), hbox);
  gtk_widget_show (hbox);

  scan_win.info_label = gtk_label_new ("0x0: 0KB");
  gtk_box_pack_start (GTK_BOX (hbox), scan_win.info_label, FALSE, FALSE, 0);
  gtk_widget_show (scan_win.info_label);

  update_param (dialog, 0);

  /* The bottom row of buttons */
  hbox = GTK_DIALOG (scan_win.shell)->action_area;

  /* The Scan button */
  button = gtk_button_new_with_label ("Scan");
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      (GtkSignalFunc) scan_dialog,
		      NULL);
  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
  gtk_widget_show (button);

  /* The Preview button */
  button = gtk_toggle_button_new_with_label ("Preview Window");
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      (GtkSignalFunc) scan_preview, NULL);
  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
  gtk_widget_show (button);

#if 0
  /* The Zoom in button */
  button = gtk_button_new_with_label ("Zoom");
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      (GtkSignalFunc) zoom_in_preview,
		      NULL);
  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
  gtk_widget_show (button);

  /* The Zoom out button */
  button = gtk_button_new_with_label ("Zoom out");
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      (GtkSignalFunc) zoom_out_preview,
		      NULL);
  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
  gtk_widget_show (button);
#endif

  pref_device_restore ();	/* restore device-settings */
  gtk_widget_show (scan_win.shell);
}

static void
ok_choose_dialog_callback (void)
{
  gtk_widget_destroy (choose_device_dialog);
  device_dialog ();
}

static void
select_device_callback (GtkWidget * widget, GdkEventButton *event,
			gpointer data)
{
  seldev = (long) data;
  if (event->type == GDK_2BUTTON_PRESS && event->button == 1)
    ok_choose_dialog_callback ();
}

static gint32
choose_device (void)
{
  GtkWidget *main_vbox, *vbox, *hbox, *button;
  GSList *owner;
  const SANE_Device *adev;
  gint i;

  choose_device_dialog = gtk_dialog_new ();
  gtk_window_set_title (GTK_WINDOW (choose_device_dialog), "Select device");

  main_vbox = GTK_DIALOG (choose_device_dialog)->vbox;

  /* The list of drivers */
  vbox = gtk_vbox_new (FALSE, 5);
  gtk_container_border_width (GTK_CONTAINER (vbox), 3);
  gtk_box_pack_start (GTK_BOX (main_vbox), vbox, TRUE, TRUE, 0);
  gtk_widget_show (vbox);

  /* The radio buttons */
  owner = NULL;
  for (i = 0; i < ndevs; i++)
    {
      adev = devlist[i];

      button = gtk_radio_button_new_with_label (owner, (char *) adev->name);
      gtk_signal_connect (GTK_OBJECT (button), "button_press_event",
			  (GtkSignalFunc) select_device_callback,
			  (void *) (long) i);
      gtk_box_pack_start (GTK_BOX (vbox), button, TRUE, TRUE, 0);
      gtk_widget_show (button);
      owner = gtk_radio_button_group (GTK_RADIO_BUTTON (button));;
    }

  /* The bottom row of buttons */
  hbox = GTK_DIALOG (choose_device_dialog)->action_area;

  /* The OK button */
  button = gtk_button_new_with_label ("OK");
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      (GtkSignalFunc) ok_choose_dialog_callback,
		      NULL);
  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
  gtk_widget_grab_default (button);
  gtk_widget_show (button);

  /* The Cancel button */
  button = gtk_button_new_with_label ("Cancel");
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      (GtkSignalFunc) files_exit_callback, NULL);
  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
  gtk_widget_show (button);

  gtk_widget_show (choose_device_dialog);
  return 0;
}

static void
usage (void)
{
    printf ("Usage: %s [OPTION]... [DEVICE]\n\
\n\
Start up graphical user interface to access SANE (Scanner Access Now\n\
Easy) devices.\n\
\n\
-h, --help                 display this help message and exit\n\
-V, --version              print version information\n", prog_name);
}

static void
init (int argc, char **argv)
{
  char filename[PATH_MAX];
  struct stat st;

  gtk_init (&argc, &argv);
#ifdef HAVE_LIBGIMP_GIMP_H
  gtk_rc_parse (gimp_gtkrc ());

  gdk_set_use_xshm (gimp_use_xshm ());
#endif

  gsg_make_path (sizeof (filename), filename, 0, "sane-style", 0, ".rc");
  if (stat (filename, &st) >= 0)
    gtk_rc_parse (filename);
  else
    {
      strncpy (filename, STRINGIFY(PATH_SANE_DATA_DIR) "/sane-style.rc",
	       sizeof (filename));
      filename[sizeof (filename) - 1] = '\0';
      if (stat (filename, &st) >= 0)
	gtk_rc_parse (filename);
    }

  sane_init (0, 0);

  if (argc > 1)
    {
      static SANE_Device dev;
      static const SANE_Device *device_list[] = { &dev, 0 };
      int ch;

      while ((ch = getopt_long (argc, argv, "ghV", long_options, 0)) != EOF)
	{
	  switch (ch)
	    {
	    case 'g':
	      printf ("%s: GIMP support missing.\n", argv[0]);
	      exit (0);

	    case 'V':
	      printf ("xscanimage (%s) %s\n", PACKAGE, VERSION);
	      exit (0);

	    case 'h':
	    default:
	      usage ();
	      exit (0);
	    }
	}

      if (optind < argc)
	{
	  memset (&dev, 0, sizeof (dev));
	  dev.name   = argv[argc - 1];
	  dev.vendor = "Unknown";
	  dev.type   = "unknown";
	  dev.model  = "unknown";

	  devlist = device_list;
	  seldev = 0;
	}
    }

  if (seldev < 0)
    sane_get_devices (&devlist, SANE_FALSE);
}

static void
interface (int argc, char **argv)
{
  scan_win.info_label = NULL;

  init (argc, argv);

  for (ndevs = 0; devlist[ndevs]; ++ndevs);

  if (seldev >= 0)
    {
      if (seldev >= ndevs)
	{
	  fprintf (stderr, "%s: device %d is unavailable.\n",
		   prog_name, seldev);
	  quit_xscan ();
	}
      device_dialog ();
      if (!dialog)
	quit_xscan ();
    }
  else
    {
      if (ndevs > 0)
	{
	  seldev = 0;
	  if (ndevs == 1)
	    {
	      device_dialog ();
	      if (!dialog)
		quit_xscan ();
	    }
	  else
	    choose_device ();
	}
      else
	{
	  fprintf (stderr, "%s: no devices available.\n", prog_name);
	  quit_xscan ();
	}
    }
  gtk_main ();
  sane_exit ();
}

int
main (int argc, char **argv)
{
  scan_win.mode = STANDALONE;

  prog_name = strrchr (argv[0], '/');
  if (prog_name)
    ++prog_name;
  else
    prog_name = argv[0];

#ifdef HAVE_LIBGIMP_GIMP_H
  {
    GPrintFunc old_print_func;
    int result;

    /* Temporarily install a print function that discards all output.
       This is to avoid annoying "you must run this program under
       gimp" messages when xscanimage gets invoked in stand-alone
       mode.  */
    old_print_func = g_set_print_handler (null_print_func);
    /* gimp_main () returns 1 if xscanimage wasn't invoked by GIMP */
    result = gimp_main (argc, argv);
    g_set_message_handler (old_print_func);
    if (result)
      interface (argc, argv);
  }
#else
  interface (argc, argv);
#endif
  return 0;
}
