summaryrefslogtreecommitdiff
path: root/src/scanner.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/scanner.c')
-rw-r--r--src/scanner.c1630
1 files changed, 1630 insertions, 0 deletions
diff --git a/src/scanner.c b/src/scanner.c
new file mode 100644
index 0000000..daf1b15
--- /dev/null
+++ b/src/scanner.c
@@ -0,0 +1,1630 @@
+/*
+ * Copyright (C) 2009 Canonical Ltd.
+ * Author: Robert Ancell <robert.ancell@canonical.com>
+ *
+ * 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 3 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <sane/sane.h>
+#include <sane/saneopts.h>
+#include <glib/gi18n.h>
+
+#include "scanner.h"
+
+/* TODO: Could indicate the start of the next page immediately after the last page is received (i.e. before the sane_cancel()) */
+
+enum {
+ UPDATE_DEVICES,
+ AUTHORIZE,
+ EXPECT_PAGE,
+ GOT_PAGE_INFO,
+ GOT_LINE,
+ SCAN_FAILED,
+ PAGE_DONE,
+ DOCUMENT_DONE,
+ SCANNING_CHANGED,
+ LAST_SIGNAL
+};
+static guint signals[LAST_SIGNAL] = { 0, };
+
+typedef struct
+{
+ Scanner *instance;
+ guint sig;
+ gpointer data;
+} SignalInfo;
+
+typedef struct
+{
+ gchar *device;
+ gdouble dpi;
+ ScanMode scan_mode;
+ gint depth;
+ gboolean type;
+ gint page_width, page_height;
+} ScanJob;
+
+typedef struct
+{
+ enum
+ {
+ REQUEST_CANCEL,
+ REQUEST_REDETECT,
+ REQUEST_START_SCAN,
+ REQUEST_QUIT
+ } type;
+ ScanJob *job;
+} ScanRequest;
+
+typedef struct
+{
+ gchar *username, *password;
+} Credentials;
+
+typedef enum
+{
+ STATE_IDLE = 0,
+ STATE_REDETECT,
+ STATE_OPEN,
+ STATE_GET_OPTION,
+ STATE_START,
+ STATE_GET_PARAMETERS,
+ STATE_READ
+} ScanState;
+
+struct ScannerPrivate
+{
+ GAsyncQueue *scan_queue, *authorize_queue;
+ GThread *thread;
+
+ gchar *default_device;
+
+ ScanState state;
+ gboolean redetect;
+
+ GList *job_queue;
+
+ /* Handle to SANE device */
+ SANE_Handle handle;
+ gchar *current_device;
+
+ SANE_Parameters parameters;
+
+ /* Last option read */
+ SANE_Int option_index;
+
+ /* Buffer for received line */
+ SANE_Byte *buffer;
+ SANE_Int buffer_size, n_used;
+
+ SANE_Int bytes_remaining, line_count, pass_number, page_number, notified_page;
+
+ gboolean scanning;
+};
+
+G_DEFINE_TYPE (Scanner, scanner, G_TYPE_OBJECT);
+
+
+/* Table of scanner objects for each thread (required for authorization callback) */
+static GHashTable *scanners;
+
+
+static gboolean
+send_signal (SignalInfo *info)
+{
+ g_signal_emit (info->instance, signals[info->sig], 0, info->data);
+
+ switch (info->sig) {
+ case UPDATE_DEVICES:
+ {
+ GList *iter, *devices = info->data;
+ for (iter = devices; iter; iter = iter->next) {
+ ScanDevice *device = iter->data;
+ g_free (device->name);
+ g_free (device->label);
+ g_free (device);
+ }
+ g_list_free (devices);
+ }
+ break;
+ case AUTHORIZE:
+ {
+ gchar *resource = info->data;
+ g_free (resource);
+ }
+ break;
+ case GOT_PAGE_INFO:
+ {
+ ScanPageInfo *page_info = info->data;
+ g_free (page_info);
+ }
+ break;
+ case GOT_LINE:
+ {
+ ScanLine *line = info->data;
+ g_free(line->data);
+ g_free(line);
+ }
+ break;
+ case SCAN_FAILED:
+ {
+ GError *error = info->data;
+ g_error_free (error);
+ }
+ break;
+ default:
+ case EXPECT_PAGE:
+ case PAGE_DONE:
+ case DOCUMENT_DONE:
+ case LAST_SIGNAL:
+ g_assert (info->data == NULL);
+ break;
+ }
+ g_free (info);
+
+ return FALSE;
+}
+
+
+/* Emit signals in main loop */
+static void
+emit_signal (Scanner *scanner, guint sig, gpointer data)
+{
+ SignalInfo *info;
+
+ info = g_malloc(sizeof(SignalInfo));
+ info->instance = scanner;
+ info->sig = sig;
+ info->data = data;
+ g_idle_add ((GSourceFunc) send_signal, info);
+}
+
+
+static void
+set_scanning (Scanner *scanner, gboolean is_scanning)
+{
+ if ((scanner->priv->scanning && !is_scanning) || (!scanner->priv->scanning && is_scanning)) {
+ scanner->priv->scanning = is_scanning;
+ emit_signal (scanner, SCANNING_CHANGED, NULL);
+ }
+}
+
+
+static gint
+get_device_weight (const gchar *device)
+{
+ /* NOTE: This is using trends in the naming of SANE devices, SANE should be able to provide this information better */
+
+ /* Use webcams as a last resort */
+ if (g_str_has_prefix (device, "vfl:"))
+ return 2;
+
+ /* Use locally connected devices first */
+ if (strstr (device, "usb"))
+ return 0;
+
+ return 1;
+}
+
+
+static gint
+compare_devices (ScanDevice *device1, ScanDevice *device2)
+{
+ gint weight1, weight2;
+
+ /* TODO: Should do some fuzzy matching on the last selected device and set that to the default */
+
+ weight1 = get_device_weight (device1->name);
+ weight2 = get_device_weight (device2->name);
+ if (weight1 != weight2)
+ return weight1 - weight2;
+
+ return strcmp (device1->label, device2->label);
+}
+
+
+static const char *
+get_status_string (SANE_Status status)
+{
+ struct {
+ SANE_Status status;
+ const char *name;
+ } status_names[] = {
+ { SANE_STATUS_GOOD, "SANE_STATUS_GOOD"},
+ { SANE_STATUS_UNSUPPORTED, "SANE_STATUS_UNSUPPORTED"},
+ { SANE_STATUS_CANCELLED, "SANE_STATUS_CANCELLED"},
+ { SANE_STATUS_DEVICE_BUSY, "SANE_STATUS_DEVICE_BUSY"},
+ { SANE_STATUS_INVAL, "SANE_STATUS_INVAL"},
+ { SANE_STATUS_EOF, "SANE_STATUS_EOF"},
+ { SANE_STATUS_JAMMED, "SANE_STATUS_JAMMED"},
+ { SANE_STATUS_NO_DOCS, "SANE_STATUS_NO_DOCS"},
+ { SANE_STATUS_COVER_OPEN, "SANE_STATUS_COVER_OPEN"},
+ { SANE_STATUS_IO_ERROR, "SANE_STATUS_IO_ERROR"},
+ { SANE_STATUS_NO_MEM, "SANE_STATUS_NO_MEM"},
+ { SANE_STATUS_ACCESS_DENIED, "SANE_STATUS_ACCESS_DENIED"},
+ { -1, NULL}
+ };
+ static char *unknown_string = NULL;
+ int i;
+
+ for (i = 0; status_names[i].name != NULL && status_names[i].status != status; i++);
+
+ if (status_names[i].name == NULL) {
+ g_free (unknown_string);
+ unknown_string = g_strdup_printf ("SANE_STATUS(%d)", status);
+ return unknown_string; /* Note result is undefined on second call to this function */
+ }
+
+ return status_names[i].name;
+}
+
+
+static const char *
+get_action_string (SANE_Action action)
+{
+ struct {
+ SANE_Action action;
+ const char *name;
+ } action_names[] = {
+ { SANE_ACTION_GET_VALUE, "SANE_ACTION_GET_VALUE" },
+ { SANE_ACTION_SET_VALUE, "SANE_ACTION_SET_VALUE" },
+ { SANE_ACTION_SET_AUTO, "SANE_ACTION_SET_AUTO" },
+ { -1, NULL}
+ };
+ static char *unknown_string = NULL;
+ int i;
+
+ for (i = 0; action_names[i].name != NULL && action_names[i].action != action; i++);
+
+ if (action_names[i].name == NULL) {
+ g_free (unknown_string);
+ unknown_string = g_strdup_printf ("SANE_ACTION(%d)", action);
+ return unknown_string; /* Note result is undefined on second call to this function */
+ }
+
+ return action_names[i].name;
+}
+
+
+static const char *
+get_frame_string (SANE_Frame frame)
+{
+ struct {
+ SANE_Frame frame;
+ const char *name;
+ } frame_names[] = {
+ { SANE_FRAME_GRAY, "SANE_FRAME_GRAY" },
+ { SANE_FRAME_RGB, "SANE_FRAME_RGB" },
+ { SANE_FRAME_RED, "SANE_FRAME_RED" },
+ { SANE_FRAME_GREEN, "SANE_FRAME_GREEN" },
+ { SANE_FRAME_BLUE, "SANE_FRAME_BLUE" },
+ { -1, NULL}
+ };
+ static char *unknown_string = NULL;
+ int i;
+
+ for (i = 0; frame_names[i].name != NULL && frame_names[i].frame != frame; i++);
+
+ if (frame_names[i].name == NULL) {
+ g_free (unknown_string);
+ unknown_string = g_strdup_printf ("SANE_FRAME(%d)", frame);
+ return unknown_string; /* Note result is undefined on second call to this function */
+ }
+
+ return frame_names[i].name;
+}
+
+
+static void
+do_redetect (Scanner *scanner)
+{
+ const SANE_Device **device_list, **device_iter;
+ SANE_Status status;
+ GList *devices = NULL;
+
+ status = sane_get_devices (&device_list, SANE_FALSE);
+ g_debug ("sane_get_devices () -> %s", get_status_string (status));
+ if (status != SANE_STATUS_GOOD) {
+ g_warning ("Unable to get SANE devices: %s", sane_strstatus(status));
+ scanner->priv->state = STATE_IDLE;
+ return;
+ }
+
+ for (device_iter = device_list; *device_iter; device_iter++) {
+ const SANE_Device *device = *device_iter;
+ ScanDevice *scan_device;
+ gchar *c, *label;
+
+ g_debug ("Device: name=\"%s\" vendor=\"%s\" model=\"%s\" type=\"%s\"",
+ device->name, device->vendor, device->model, device->type);
+
+ scan_device = g_malloc0 (sizeof (ScanDevice));
+
+ scan_device->name = g_strdup (device->name);
+
+ /* Abbreviate HP as it is a long string and does not match what is on the physical scanner */
+ if (strcmp (device->vendor, "Hewlett-Packard") == 0)
+ label = g_strdup_printf ("HP %s", device->model);
+ else
+ label = g_strdup_printf ("%s %s", device->vendor, device->model);
+
+ /* Replace underscored in name */
+ for (c = label; *c; c++)
+ if (*c == '_')
+ *c = ' ';
+
+ scan_device->label = label;
+
+ devices = g_list_append (devices, scan_device);
+ }
+
+ /* Sort devices by priority */
+ devices = g_list_sort (devices, (GCompareFunc) compare_devices);
+
+ scanner->priv->redetect = FALSE;
+ scanner->priv->state = STATE_IDLE;
+
+ g_free (scanner->priv->default_device);
+ if (devices) {
+ ScanDevice *device = g_list_nth_data (devices, 0);
+ scanner->priv->default_device = g_strdup (device->name);
+ }
+ else
+ scanner->priv->default_device = NULL;
+
+ emit_signal (scanner, UPDATE_DEVICES, devices);
+}
+
+
+static gboolean
+control_option (SANE_Handle handle, const SANE_Option_Descriptor *option, SANE_Int index, SANE_Action action, void *value)
+{
+ SANE_Status status;
+ gchar *old_value;
+
+ switch (option->type) {
+ case SANE_TYPE_BOOL:
+ old_value = g_strdup_printf (*((SANE_Bool *) value) ? "SANE_TRUE" : "SANE_FALSE");
+ break;
+ case SANE_TYPE_INT:
+ old_value = g_strdup_printf ("%d", *((SANE_Int *) value));
+ break;
+ case SANE_TYPE_FIXED:
+ old_value = g_strdup_printf ("%f", SANE_UNFIX (*((SANE_Fixed *) value)));
+ break;
+ case SANE_TYPE_STRING:
+ old_value = g_strdup_printf ("\"%s\"", (char *) value);
+ break;
+ default:
+ old_value = g_strdup ("?");
+ break;
+ }
+
+ status = sane_control_option (handle, index, action, value, NULL);
+ switch (option->type) {
+ case SANE_TYPE_BOOL:
+ g_debug ("sane_control_option (%d, %s, %s) -> (%s, %s)",
+ index, get_action_string (action),
+ *((SANE_Bool *) value) ? "SANE_TRUE" : "SANE_FALSE",
+ get_status_string (status),
+ old_value);
+ break;
+ case SANE_TYPE_INT:
+ g_debug ("sane_control_option (%d, %s, %d) -> (%s, %s)",
+ index, get_action_string (action),
+ *((SANE_Int *) value),
+ get_status_string (status),
+ old_value);
+ break;
+ case SANE_TYPE_FIXED:
+ g_debug ("sane_control_option (%d, %s, %f) -> (%s, %s)",
+ index, get_action_string (action),
+ SANE_UNFIX (*((SANE_Fixed *) value)),
+ get_status_string (status),
+ old_value);
+ break;
+ case SANE_TYPE_STRING:
+ g_debug ("sane_control_option (%d, %s, \"%s\") -> (%s, %s)",
+ index, get_action_string (action),
+ (char *) value,
+ get_status_string (status),
+ old_value);
+ break;
+ default:
+ break;
+ }
+ g_free (old_value);
+
+ if (status != SANE_STATUS_GOOD)
+ g_warning ("Error setting option %s: %s", option->name, sane_strstatus(status));
+
+ return status == SANE_STATUS_GOOD;
+}
+
+
+static gboolean
+set_default_option (SANE_Handle handle, const SANE_Option_Descriptor *option, SANE_Int option_index)
+{
+ SANE_Status status;
+
+ status = sane_control_option (handle, option_index, SANE_ACTION_SET_AUTO, NULL, NULL);
+ g_debug ("sane_control_option (%d, SANE_ACTION_SET_AUTO) -> %s",
+ option_index, get_status_string (status));
+ if (status != SANE_STATUS_GOOD)
+ g_warning ("Error setting default option %s: %s", option->name, sane_strstatus(status));
+
+ return status == SANE_STATUS_GOOD;
+}
+
+
+static void
+set_bool_option (SANE_Handle handle, const SANE_Option_Descriptor *option, SANE_Int option_index, SANE_Bool value, SANE_Bool *result)
+{
+ SANE_Bool v = value;
+ g_return_if_fail (option->type == SANE_TYPE_BOOL);
+ control_option (handle, option, option_index, SANE_ACTION_SET_VALUE, &v);
+ if (result)
+ *result = v;
+}
+
+
+static void
+set_int_option (SANE_Handle handle, const SANE_Option_Descriptor *option, SANE_Int option_index, SANE_Int value, SANE_Int *result)
+{
+ SANE_Int v = value;
+
+ g_return_if_fail (option->type == SANE_TYPE_INT);
+
+ if (option->constraint_type == SANE_CONSTRAINT_RANGE) {
+ if (option->constraint.range->quant)
+ v *= option->constraint.range->quant;
+ if (v < option->constraint.range->min)
+ v = option->constraint.range->min;
+ if (v > option->constraint.range->max)
+ v = option->constraint.range->max;
+ }
+ else if (option->constraint_type == SANE_CONSTRAINT_WORD_LIST) {
+ int i;
+ SANE_Int distance = INT_MAX, nearest = 0;
+
+ /* Find nearest value to requested */
+ for (i = 0; i < option->constraint.word_list[0]; i++) {
+ SANE_Int x = option->constraint.word_list[i+1];
+ if (abs (x - v) < distance) {
+ distance = abs (x - v);
+ nearest = x;
+ }
+ }
+ v = nearest;
+ }
+
+ control_option (handle, option, option_index, SANE_ACTION_SET_VALUE, &v);
+ if (result)
+ *result = v;
+}
+
+
+static void
+set_fixed_option (SANE_Handle handle, const SANE_Option_Descriptor *option, SANE_Int option_index, gdouble value, gdouble *result)
+{
+ gdouble v = value;
+ SANE_Fixed v_fixed;
+
+ g_return_if_fail (option->type == SANE_TYPE_FIXED);
+
+ if (option->constraint_type == SANE_CONSTRAINT_RANGE) {
+ double min = SANE_UNFIX (option->constraint.range->min);
+ double max = SANE_UNFIX (option->constraint.range->max);
+
+ if (v < min)
+ v = min;
+ if (v > max)
+ v = max;
+ }
+ else if (option->constraint_type == SANE_CONSTRAINT_WORD_LIST) {
+ int i;
+ double distance = DBL_MAX, nearest = 0.0;
+
+ /* Find nearest value to requested */
+ for (i = 0; i < option->constraint.word_list[0]; i++) {
+ double x = SANE_UNFIX (option->constraint.word_list[i+1]);
+ if (fabs (x - v) < distance) {
+ distance = fabs (x - v);
+ nearest = x;
+ }
+ }
+ v = nearest;
+ }
+
+ v_fixed = SANE_FIX (v);
+ control_option (handle, option, option_index, SANE_ACTION_SET_VALUE, &v_fixed);
+ if (result)
+ *result = SANE_UNFIX (v_fixed);
+}
+
+
+static gboolean
+set_string_option (SANE_Handle handle, const SANE_Option_Descriptor *option, SANE_Int option_index, const char *value, char **result)
+{
+ char *string;
+ gsize value_size, size;
+ gboolean error;
+
+ g_return_val_if_fail (option->type == SANE_TYPE_STRING, FALSE);
+
+ value_size = strlen (value) + 1;
+ size = option->size > value_size ? option->size : value_size;
+ string = g_malloc(sizeof(char) * size);
+ strcpy (string, value);
+ error = control_option (handle, option, option_index, SANE_ACTION_SET_VALUE, string);
+ if (result)
+ *result = string;
+ else
+ g_free (string);
+
+ return error;
+}
+
+
+static gboolean
+set_constrained_string_option (SANE_Handle handle, const SANE_Option_Descriptor *option, SANE_Int option_index, const char *values[], char **result)
+{
+ gint i, j;
+
+ g_return_val_if_fail (option->type == SANE_TYPE_STRING, FALSE);
+ g_return_val_if_fail (option->constraint_type == SANE_CONSTRAINT_STRING_LIST, FALSE);
+
+ for (i = 0; values[i] != NULL; i++) {
+ for (j = 0; option->constraint.string_list[j]; j++) {
+ if (strcmp (values[i], option->constraint.string_list[j]) == 0)
+ break;
+ }
+
+ if (option->constraint.string_list[j] != NULL)
+ return set_string_option (handle, option, option_index, values[i], result);
+ }
+
+ return FALSE;
+}
+
+
+static void
+log_option (SANE_Int index, const SANE_Option_Descriptor *option)
+{
+ GString *string;
+ SANE_Word i;
+ SANE_Int cap;
+
+ string = g_string_new ("");
+
+ g_string_append_printf (string, "Option %d:", index);
+
+ if (option->name)
+ g_string_append_printf (string, " name='%s'", option->name);
+
+ if (option->title)
+ g_string_append_printf (string, " title='%s'", option->title);
+
+ switch (option->type) {
+ case SANE_TYPE_BOOL:
+ g_string_append (string, " type=bool");
+ break;
+ case SANE_TYPE_INT:
+ g_string_append (string, " type=int");
+ break;
+ case SANE_TYPE_FIXED:
+ g_string_append (string, " type=fixed");
+ break;
+ case SANE_TYPE_STRING:
+ g_string_append (string, " type=string");
+ break;
+ case SANE_TYPE_BUTTON:
+ g_string_append (string, " type=button");
+ break;
+ case SANE_TYPE_GROUP:
+ g_string_append (string, " type=group");
+ break;
+ default:
+ g_string_append_printf (string, " type=%d", option->type);
+ break;
+ }
+
+ g_string_append_printf (string, " size=%d", option->size);
+
+ switch (option->unit) {
+ case SANE_UNIT_NONE:
+ break;
+ case SANE_UNIT_PIXEL:
+ g_string_append (string, " unit=pixels");
+ break;
+ case SANE_UNIT_BIT:
+ g_string_append (string, " unit=bits");
+ break;
+ case SANE_UNIT_MM:
+ g_string_append (string, " unit=mm");
+ break;
+ case SANE_UNIT_DPI:
+ g_string_append (string, " unit=dpi");
+ break;
+ case SANE_UNIT_PERCENT:
+ g_string_append (string, " unit=percent");
+ break;
+ case SANE_UNIT_MICROSECOND:
+ g_string_append (string, " unit=microseconds");
+ break;
+ default:
+ g_string_append_printf (string, " unit=%d", option->unit);
+ break;
+ }
+
+ switch (option->constraint_type) {
+ case SANE_CONSTRAINT_RANGE:
+ if (option->type == SANE_TYPE_FIXED)
+ g_string_append_printf (string, " min=%f, max=%f, quant=%d",
+ SANE_UNFIX (option->constraint.range->min), SANE_UNFIX (option->constraint.range->max),
+ option->constraint.range->quant);
+ else
+ g_string_append_printf (string, " min=%d, max=%d, quant=%d",
+ option->constraint.range->min, option->constraint.range->max,
+ option->constraint.range->quant);
+ break;
+ case SANE_CONSTRAINT_WORD_LIST:
+ g_string_append (string, " values=[");
+ for (i = 0; i < option->constraint.word_list[0]; i++) {
+ if (i != 0)
+ g_string_append (string, ", ");
+ if (option->type == SANE_TYPE_INT)
+ g_string_append_printf (string, "%d", option->constraint.word_list[i+1]);
+ else
+ g_string_append_printf (string, "%f", SANE_UNFIX (option->constraint.word_list[i+1]));
+ }
+ g_string_append (string, "]");
+ break;
+ case SANE_CONSTRAINT_STRING_LIST:
+ g_string_append (string, " values=[");
+ for (i = 0; option->constraint.string_list[i]; i++) {
+ if (i != 0)
+ g_string_append (string, ", ");
+ g_string_append_printf (string, "\"%s\"", option->constraint.string_list[i]);
+ }
+ g_string_append (string, "]");
+ break;
+ default:
+ break;
+ }
+
+ cap = option->cap;
+ if (cap) {
+ struct {
+ SANE_Int cap;
+ const char *name;
+ } caps[] = {
+ { SANE_CAP_SOFT_SELECT, "soft-select"},
+ { SANE_CAP_HARD_SELECT, "hard-select"},
+ { SANE_CAP_SOFT_DETECT, "soft-detect"},
+ { SANE_CAP_EMULATED, "emulated"},
+ { SANE_CAP_AUTOMATIC, "automatic"},
+ { SANE_CAP_INACTIVE, "inactive"},
+ { SANE_CAP_ADVANCED, "advanced"},
+ { 0, NULL}
+ };
+ int i, n = 0;
+
+ g_string_append (string, " cap=");
+ for (i = 0; caps[i].cap > 0; i++) {
+ if (cap & caps[i].cap) {
+ cap &= ~caps[i].cap;
+ if (n != 0)
+ g_string_append (string, ",");
+ g_string_append (string, caps[i].name);
+ n++;
+ }
+ }
+ /* Unknown capabilities */
+ if (cap) {
+ if (n != 0)
+ g_string_append (string, ",");
+ g_string_append_printf (string, "%x", cap);
+ }
+ }
+
+ g_debug ("%s", string->str);
+ g_string_free (string, TRUE);
+
+ if (option->desc)
+ g_debug (" Description: %s", option->desc);
+}
+
+
+static void
+authorization_cb (SANE_String_Const resource, SANE_Char username[SANE_MAX_USERNAME_LEN], SANE_Char password[SANE_MAX_PASSWORD_LEN])
+{
+ Scanner *scanner;
+ Credentials *credentials;
+
+ scanner = g_hash_table_lookup (scanners, g_thread_self ());
+
+ emit_signal (scanner, AUTHORIZE, g_strdup (resource));
+
+ credentials = g_async_queue_pop (scanner->priv->authorize_queue);
+ strncpy (username, credentials->username, SANE_MAX_USERNAME_LEN);
+ strncpy (password, credentials->password, SANE_MAX_PASSWORD_LEN);
+ g_free (credentials);
+}
+
+
+void
+scanner_authorize (Scanner *scanner, const gchar *username, const gchar *password)
+{
+ Credentials *credentials;
+
+ credentials = g_malloc (sizeof (Credentials));
+ credentials->username = g_strdup (username);
+ credentials->password = g_strdup (password);
+ g_async_queue_push (scanner->priv->authorize_queue, credentials);
+}
+
+
+static void
+close_device (Scanner *scanner)
+{
+ GList *iter;
+
+ if (scanner->priv->handle) {
+ sane_cancel (scanner->priv->handle);
+ g_debug ("sane_cancel ()");
+
+ sane_close (scanner->priv->handle);
+ g_debug ("sane_close ()");
+ scanner->priv->handle = NULL;
+ }
+
+ g_free (scanner->priv->buffer);
+ scanner->priv->buffer = NULL;
+
+ for (iter = scanner->priv->job_queue; iter; iter = iter->next) {
+ ScanJob *job = (ScanJob *) iter->data;
+ g_free (job->device);
+ g_free (job);
+ }
+ g_list_free (scanner->priv->job_queue);
+ scanner->priv->job_queue = NULL;
+
+ set_scanning (scanner, FALSE);
+}
+
+static void
+fail_scan (Scanner *scanner, gint error_code, const gchar *error_string)
+{
+ close_device (scanner);
+ scanner->priv->state = STATE_IDLE;
+ emit_signal (scanner, SCAN_FAILED, g_error_new (SCANNER_TYPE, error_code, "%s", error_string));
+}
+
+
+static gboolean
+handle_requests (Scanner *scanner)
+{
+ gint request_count = 0;
+
+ /* Redetect when idle */
+ if (scanner->priv->state == STATE_IDLE && scanner->priv->redetect)
+ scanner->priv->state = STATE_REDETECT;
+
+ /* Process all requests */
+ while (TRUE) {
+ ScanRequest *request = NULL;
+
+ if ((scanner->priv->state == STATE_IDLE && request_count == 0) ||
+ g_async_queue_length (scanner->priv->scan_queue) > 0)
+ request = g_async_queue_pop (scanner->priv->scan_queue);
+ else
+ return TRUE;
+
+ g_debug ("Processing request");
+ request_count++;
+
+ switch (request->type) {
+ case REQUEST_REDETECT:
+ break;
+
+ case REQUEST_START_SCAN:
+ scanner->priv->job_queue = g_list_append (scanner->priv->job_queue, request->job);
+ break;
+
+ case REQUEST_CANCEL:
+ fail_scan (scanner, SANE_STATUS_CANCELLED, "Scan cancelled - do not report this error");
+ break;
+
+ case REQUEST_QUIT:
+ close_device (scanner);
+ g_free (request);
+ return FALSE;
+ }
+
+ g_free (request);
+ }
+
+ return TRUE;
+}
+
+
+static void
+do_open (Scanner *scanner)
+{
+ SANE_Status status;
+ ScanJob *job;
+
+ job = (ScanJob *) scanner->priv->job_queue->data;
+
+ scanner->priv->line_count = 0;
+ scanner->priv->pass_number = 0;
+ scanner->priv->page_number = 0;
+ scanner->priv->notified_page = -1;
+ scanner->priv->option_index = 0;
+
+ if (!job->device && scanner->priv->default_device)
+ job->device = g_strdup (scanner->priv->default_device);
+
+ if (!job->device) {
+ g_warning ("No scan device available");
+ fail_scan (scanner, status,
+ /* Error displayed when no scanners to scan with */
+ _("No scanners available. Please connect a scanner."));
+ return;
+ }
+
+ /* See if we can use the already open device */
+ if (scanner->priv->handle) {
+ if (strcmp (scanner->priv->current_device, job->device) == 0) {
+ scanner->priv->state = STATE_GET_OPTION;
+ return;
+ }
+
+ sane_close (scanner->priv->handle);
+ g_debug ("sane_close ()");
+ scanner->priv->handle = NULL;
+ }
+
+ g_free (scanner->priv->current_device);
+ scanner->priv->current_device = NULL;
+
+ status = sane_open (job->device, &scanner->priv->handle);
+ g_debug ("sane_open (\"%s\") -> %s", job->device, get_status_string (status));
+
+ if (status != SANE_STATUS_GOOD) {
+ g_warning ("Unable to get open device: %s", sane_strstatus (status));
+ scanner->priv->handle = NULL;
+ fail_scan (scanner, status,
+ /* Error displayed when cannot connect to scanner */
+ _("Unable to connect to scanner"));
+ return;
+ }
+
+ scanner->priv->current_device = g_strdup (job->device);
+ scanner->priv->state = STATE_GET_OPTION;
+}
+
+
+static void
+do_get_option (Scanner *scanner)
+{
+ const SANE_Option_Descriptor *option;
+ SANE_Int option_index;
+ ScanJob *job;
+
+ job = (ScanJob *) scanner->priv->job_queue->data;
+
+ option = sane_get_option_descriptor (scanner->priv->handle, scanner->priv->option_index);
+ g_debug ("sane_get_option_descriptor (%d)", scanner->priv->option_index);
+ option_index = scanner->priv->option_index;
+ scanner->priv->option_index++;
+
+ if (!option) {
+ scanner->priv->state = STATE_START;
+ return;
+ }
+
+ log_option (option_index, option);
+
+ /* Ignore groups */
+ if (option->type == SANE_TYPE_GROUP)
+ return;
+
+ /* Option disabled */
+ if (option->cap & SANE_CAP_INACTIVE)
+ return;
+
+ /* Some options are unnammed (e.g. Option 0) */
+ if (option->name == NULL)
+ return;
+
+ if (strcmp (option->name, SANE_NAME_SCAN_RESOLUTION) == 0) {
+ if (option->type == SANE_TYPE_FIXED) {
+ set_fixed_option (scanner->priv->handle, option, option_index, job->dpi, &job->dpi);
+ }
+ else {
+ SANE_Int dpi;
+ set_int_option (scanner->priv->handle, option, option_index, job->dpi, &dpi);
+ job->dpi = dpi;
+ }
+ }
+ else if (strcmp (option->name, SANE_NAME_SCAN_SOURCE) == 0) {
+ const char *flatbed_sources[] =
+ {
+ "Flatbed",
+ SANE_I18N ("Flatbed"),
+ "FlatBed",
+ "Normal",
+ SANE_I18N ("Normal"),
+ NULL
+ };
+
+ const char *adf_sources[] =
+ {
+ "Automatic Document Feeder",
+ SANE_I18N ("Automatic Document Feeder"),
+ "ADF",
+ "Automatic Document Feeder(left aligned)", /* Seen in the proprietary brother3 driver */
+ "Automatic Document Feeder(centrally aligned)", /* Seen in the proprietary brother3 driver */
+ NULL
+ };
+
+ const char *adf_front_sources[] =
+ {
+ "ADF Front",
+ SANE_I18N ("ADF Front"),
+ NULL
+ };
+
+ const char *adf_back_sources[] =
+ {
+ "ADF Back",
+ SANE_I18N ("ADF Back"),
+ NULL
+ };
+
+ const char *adf_duplex_sources[] =
+ {
+ "ADF Duplex",
+ SANE_I18N ("ADF Duplex"),
+ NULL
+ };
+
+ switch (job->type) {
+ case SCAN_SINGLE:
+ if (!set_default_option (scanner->priv->handle, option, option_index))
+ if (!set_constrained_string_option (scanner->priv->handle, option, option_index, flatbed_sources, NULL))
+ g_warning ("Unable to set single page source, please file a bug");
+ break;
+ case SCAN_ADF_FRONT:
+ if (!set_constrained_string_option (scanner->priv->handle, option, option_index, adf_front_sources, NULL))
+ if (!!set_constrained_string_option (scanner->priv->handle, option, option_index, adf_sources, NULL))
+ g_warning ("Unable to set front ADF source, please file a bug");
+ break;
+ case SCAN_ADF_BACK:
+ if (!set_constrained_string_option (scanner->priv->handle, option, option_index, adf_back_sources, NULL))
+ if (!set_constrained_string_option (scanner->priv->handle, option, option_index, adf_sources, NULL))
+ g_warning ("Unable to set back ADF source, please file a bug");
+ break;
+ case SCAN_ADF_BOTH:
+ if (!set_constrained_string_option (scanner->priv->handle, option, option_index, adf_duplex_sources, NULL))
+ if (!set_constrained_string_option (scanner->priv->handle, option, option_index, adf_sources, NULL))
+ g_warning ("Unable to set duplex ADF source, please file a bug");
+ break;
+ }
+ }
+ else if (strcmp (option->name, SANE_NAME_BIT_DEPTH) == 0) {
+ if (job->depth > 0)
+ set_int_option (scanner->priv->handle, option, option_index, job->depth, NULL);
+ }
+ else if (strcmp (option->name, SANE_NAME_SCAN_MODE) == 0) {
+ /* The names of scan modes often used in drivers, as taken from the sane-backends source */
+ const char *color_scan_modes[] =
+ {
+ SANE_VALUE_SCAN_MODE_COLOR,
+ "Color",
+ "24bit Color", /* Seen in the proprietary brother3 driver */
+ NULL
+ };
+ const char *gray_scan_modes[] =
+ {
+ SANE_VALUE_SCAN_MODE_GRAY,
+ "Gray",
+ "Grayscale",
+ SANE_I18N ("Grayscale"),
+ NULL
+ };
+ const char *lineart_scan_modes[] =
+ {
+ SANE_VALUE_SCAN_MODE_LINEART,
+ "Lineart",
+ "LineArt",
+ SANE_I18N ("LineArt"),
+ "Black & White",
+ SANE_I18N ("Black & White"),
+ "Binary",
+ SANE_I18N ("Binary"),
+ "Thresholded",
+ SANE_VALUE_SCAN_MODE_GRAY,
+ "Gray",
+ "Grayscale",
+ SANE_I18N ("Grayscale"),
+ NULL
+ };
+
+ switch (job->scan_mode) {
+ case SCAN_MODE_COLOR:
+ if (!set_constrained_string_option (scanner->priv->handle, option, option_index, color_scan_modes, NULL))
+ g_warning ("Unable to set Color mode, please file a bug");
+ break;
+ case SCAN_MODE_GRAY:
+ if (!set_constrained_string_option (scanner->priv->handle, option, option_index, gray_scan_modes, NULL))
+ g_warning ("Unable to set Gray mode, please file a bug");
+ break;
+ case SCAN_MODE_LINEART:
+ if (!set_constrained_string_option (scanner->priv->handle, option, option_index, lineart_scan_modes, NULL))
+ g_warning ("Unable to set Lineart mode, please file a bug");
+ break;
+ default:
+ break;
+ }
+ }
+ /* Disable compression, we will compress after scanning */
+ else if (strcmp (option->name, "compression") == 0) {
+ const char *disable_compression_names[] =
+ {
+ SANE_I18N ("None"),
+ SANE_I18N ("none"),
+ "None",
+ "none",
+ NULL
+ };
+
+ if (!set_constrained_string_option (scanner->priv->handle, option, option_index, disable_compression_names, NULL))
+ g_warning ("Unable to disable compression, please file a bug");
+ }
+ /* Always use maximum scan area - some scanners default to using partial areas. This should be patched in sane-backends */
+ else if (strcmp (option->name, SANE_NAME_SCAN_BR_X) == 0 ||
+ strcmp (option->name, SANE_NAME_SCAN_BR_Y) == 0) {
+ switch (option->constraint_type)
+ {
+ case SANE_CONSTRAINT_RANGE:
+ if (option->type == SANE_TYPE_FIXED)
+ set_fixed_option (scanner->priv->handle, option, option_index, SANE_UNFIX (option->constraint.range->max), NULL);
+ else
+ set_int_option (scanner->priv->handle, option, option_index, option->constraint.range->max, NULL);
+ break;
+ default:
+ break;
+ }
+ }
+ else if (strcmp (option->name, SANE_NAME_PAGE_WIDTH) == 0) {
+ if (job->page_width > 0.0) {
+ if (option->type == SANE_TYPE_FIXED)
+ set_fixed_option (scanner->priv->handle, option, option_index, job->page_width / 10.0, NULL);
+ else
+ set_int_option (scanner->priv->handle, option, option_index, job->page_width / 10, NULL);
+ }
+ }
+ else if (strcmp (option->name, SANE_NAME_PAGE_HEIGHT) == 0) {
+ if (job->page_height > 0.0) {
+ if (option->type == SANE_TYPE_FIXED)
+ set_fixed_option (scanner->priv->handle, option, option_index, job->page_height / 10.0, NULL);
+ else
+ set_int_option (scanner->priv->handle, option, option_index, job->page_height / 10, NULL);
+ }
+ }
+
+ /* Test scanner options (hoping will not effect other scanners...) */
+ if (strcmp (scanner->priv->current_device, "test") == 0) {
+ if (strcmp (option->name, "hand-scanner") == 0) {
+ set_bool_option (scanner->priv->handle, option, option_index, FALSE, NULL);
+ }
+ else if (strcmp (option->name, "three-pass") == 0) {
+ set_bool_option (scanner->priv->handle, option, option_index, FALSE, NULL);
+ }
+ else if (strcmp (option->name, "test-picture") == 0) {
+ set_string_option (scanner->priv->handle, option, option_index, "Color pattern", NULL);
+ }
+ else if (strcmp (option->name, "read-delay") == 0) {
+ set_bool_option (scanner->priv->handle, option, option_index, TRUE, NULL);
+ }
+ else if (strcmp (option->name, "read-delay-duration") == 0) {
+ set_int_option (scanner->priv->handle, option, option_index, 200000, NULL);
+ }
+ }
+}
+
+
+static void
+do_complete_document (Scanner *scanner)
+{
+ ScanJob *job;
+
+ job = (ScanJob *) scanner->priv->job_queue->data;
+ g_free (job->device);
+ g_free (job);
+ scanner->priv->job_queue = g_list_remove_link (scanner->priv->job_queue, scanner->priv->job_queue);
+
+ scanner->priv->state = STATE_IDLE;
+
+ /* Continue onto the next job */
+ if (scanner->priv->job_queue) {
+ scanner->priv->state = STATE_OPEN;
+ return;
+ }
+
+ /* Trigger timeout to close */
+ // TODO
+
+ emit_signal (scanner, DOCUMENT_DONE, NULL);
+ set_scanning (scanner, FALSE);
+}
+
+
+static void
+do_start (Scanner *scanner)
+{
+ SANE_Status status;
+
+ emit_signal (scanner, EXPECT_PAGE, NULL);
+
+ status = sane_start (scanner->priv->handle);
+ g_debug ("sane_start (page=%d, pass=%d) -> %s", scanner->priv->page_number, scanner->priv->pass_number, get_status_string (status));
+ if (status == SANE_STATUS_GOOD) {
+ scanner->priv->state = STATE_GET_PARAMETERS;
+ }
+ else if (status == SANE_STATUS_NO_DOCS) {
+ do_complete_document (scanner);
+ }
+ else {
+ g_warning ("Unable to start device: %s", sane_strstatus (status));
+ fail_scan (scanner, status,
+ /* Error display when unable to start scan */
+ _("Unable to start scan"));
+ }
+}
+
+
+static void
+do_get_parameters (Scanner *scanner)
+{
+ SANE_Status status;
+ ScanPageInfo *info;
+ ScanJob *job;
+
+ status = sane_get_parameters (scanner->priv->handle, &scanner->priv->parameters);
+ g_debug ("sane_get_parameters () -> %s", get_status_string (status));
+ if (status != SANE_STATUS_GOOD) {
+ g_warning ("Unable to get device parameters: %s", sane_strstatus (status));
+ fail_scan (scanner, status,
+ /* Error displayed when communication with scanner broken */
+ _("Error communicating with scanner"));
+ return;
+ }
+
+ job = (ScanJob *) scanner->priv->job_queue->data;
+
+ g_debug ("Parameters: format=%s last_frame=%s bytes_per_line=%d pixels_per_line=%d lines=%d depth=%d",
+ get_frame_string (scanner->priv->parameters.format),
+ scanner->priv->parameters.last_frame ? "SANE_TRUE" : "SANE_FALSE",
+ scanner->priv->parameters.bytes_per_line,
+ scanner->priv->parameters.pixels_per_line,
+ scanner->priv->parameters.lines,
+ scanner->priv->parameters.depth);
+
+ info = g_malloc(sizeof(ScanPageInfo));
+ info->width = scanner->priv->parameters.pixels_per_line;
+ info->height = scanner->priv->parameters.lines;
+ info->depth = scanner->priv->parameters.depth;
+ info->dpi = job->dpi; // FIXME: This is the requested DPI, not the actual DPI
+ info->device = g_strdup (scanner->priv->current_device);
+
+ if (scanner->priv->page_number != scanner->priv->notified_page) {
+ emit_signal (scanner, GOT_PAGE_INFO, info);
+ scanner->priv->notified_page = scanner->priv->page_number;
+ }
+
+ /* Prepare for read */
+ scanner->priv->buffer_size = scanner->priv->parameters.bytes_per_line + 1; /* Use +1 so buffer is not resized if driver returns one line per read */
+ scanner->priv->buffer = g_malloc (sizeof(SANE_Byte) * scanner->priv->buffer_size);
+ scanner->priv->n_used = 0;
+ scanner->priv->line_count = 0;
+ scanner->priv->pass_number = 0;
+ scanner->priv->state = STATE_READ;
+}
+
+
+static void
+do_complete_page (Scanner *scanner)
+{
+ ScanJob *job;
+
+ emit_signal (scanner, PAGE_DONE, NULL);
+
+ job = (ScanJob *) scanner->priv->job_queue->data;
+
+ /* If multi-pass then scan another page */
+ if (!scanner->priv->parameters.last_frame) {
+ scanner->priv->pass_number++;
+ scanner->priv->state = STATE_START;
+ return;
+ }
+
+ /* Go back for another page */
+ if (job->type != SCAN_SINGLE) {
+ scanner->priv->page_number++;
+ scanner->priv->pass_number = 0;
+ emit_signal (scanner, PAGE_DONE, NULL);
+ scanner->priv->state = STATE_START;
+ return;
+ }
+
+ sane_cancel (scanner->priv->handle);
+ g_debug ("sane_cancel ()");
+
+ do_complete_document (scanner);
+}
+
+
+static void
+do_read (Scanner *scanner)
+{
+ SANE_Status status;
+ SANE_Int n_to_read, n_read;
+ gboolean full_read = FALSE;
+
+ /* Read as many bytes as we expect */
+ n_to_read = scanner->priv->buffer_size - scanner->priv->n_used;
+
+ status = sane_read (scanner->priv->handle,
+ scanner->priv->buffer + scanner->priv->n_used,
+ n_to_read, &n_read);
+ g_debug ("sane_read (%d) -> (%s, %d)", n_to_read, get_status_string (status), n_read);
+
+ /* End of variable length frame */
+ if (status == SANE_STATUS_EOF &&
+ scanner->priv->parameters.lines == -1 &&
+ scanner->priv->bytes_remaining == scanner->priv->parameters.bytes_per_line) {
+ do_complete_page (scanner);
+ return;
+ }
+
+ /* Communication error */
+ if (status != SANE_STATUS_GOOD) {
+ g_warning ("Unable to read frame from device: %s", sane_strstatus (status));
+ fail_scan (scanner, status,
+ /* Error displayed when communication with scanner broken */
+ _("Error communicating with scanner"));
+ return;
+ }
+
+ if (scanner->priv->n_used == 0 && n_read == scanner->priv->buffer_size)
+ full_read = TRUE;
+ scanner->priv->n_used += n_read;
+
+ /* Feed out lines */
+ if (scanner->priv->n_used >= scanner->priv->parameters.bytes_per_line) {
+ ScanLine *line;
+
+ line = g_malloc(sizeof(ScanLine));
+ switch (scanner->priv->parameters.format) {
+ case SANE_FRAME_GRAY:
+ line->format = LINE_GRAY;
+ break;
+ case SANE_FRAME_RGB:
+ line->format = LINE_RGB;
+ break;
+ case SANE_FRAME_RED:
+ line->format = LINE_RED;
+ break;
+ case SANE_FRAME_GREEN:
+ line->format = LINE_GREEN;
+ break;
+ case SANE_FRAME_BLUE:
+ line->format = LINE_BLUE;
+ break;
+ }
+ line->width = scanner->priv->parameters.pixels_per_line;
+ line->depth = scanner->priv->parameters.depth;
+ line->data = scanner->priv->buffer;
+ line->data_length = scanner->priv->parameters.bytes_per_line;
+ line->number = scanner->priv->line_count;
+ line->n_lines = scanner->priv->n_used / line->data_length;
+
+ scanner->priv->buffer = NULL;
+ scanner->priv->line_count += line->n_lines;
+
+ /* On last line */
+ if (scanner->priv->parameters.lines > 0 && scanner->priv->line_count >= scanner->priv->parameters.lines) {
+ emit_signal (scanner, GOT_LINE, line);
+ do_complete_page (scanner);
+ }
+ else {
+ int i, n_remaining;
+
+ /* Increase buffer size if did full read */
+ if (full_read)
+ scanner->priv->buffer_size += scanner->priv->parameters.bytes_per_line;
+
+ scanner->priv->buffer = g_malloc(sizeof(SANE_Byte) * scanner->priv->buffer_size);
+ n_remaining = scanner->priv->n_used - (line->n_lines * line->data_length);
+ scanner->priv->n_used = 0;
+ for (i = 0; i < n_remaining; i++) {
+ scanner->priv->buffer[i] = line->data[i + (line->n_lines * line->data_length)];
+ scanner->priv->n_used++;
+ }
+ emit_signal (scanner, GOT_LINE, line);
+ }
+ }
+}
+
+
+static gpointer
+scan_thread (Scanner *scanner)
+{
+ SANE_Status status;
+ SANE_Int version_code;
+
+ g_hash_table_insert (scanners, g_thread_self (), scanner);
+
+ scanner->priv->state = STATE_IDLE;
+
+ status = sane_init (&version_code, authorization_cb);
+ g_debug ("sane_init () -> %s", get_status_string (status));
+ if (status != SANE_STATUS_GOOD) {
+ g_warning ("Unable to initialize SANE backend: %s", sane_strstatus(status));
+ return FALSE;
+ }
+ g_debug ("SANE version %d.%d.%d",
+ SANE_VERSION_MAJOR(version_code),
+ SANE_VERSION_MINOR(version_code),
+ SANE_VERSION_BUILD(version_code));
+
+ /* Scan for devices on first start */
+ scanner_redetect (scanner);
+
+ while (handle_requests (scanner)) {
+ switch (scanner->priv->state) {
+ case STATE_IDLE:
+ if (scanner->priv->job_queue) {
+ set_scanning (scanner, TRUE);
+ scanner->priv->state = STATE_OPEN;
+ }
+ break;
+ case STATE_REDETECT:
+ do_redetect (scanner);
+ break;
+ case STATE_OPEN:
+ do_open (scanner);
+ break;
+ case STATE_GET_OPTION:
+ do_get_option (scanner);
+ break;
+ case STATE_START:
+ do_start (scanner);
+ break;
+ case STATE_GET_PARAMETERS:
+ do_get_parameters (scanner);
+ break;
+ case STATE_READ:
+ do_read (scanner);
+ break;
+ }
+ }
+
+ return NULL;
+}
+
+
+Scanner *
+scanner_new ()
+{
+ return g_object_new (SCANNER_TYPE, NULL);
+}
+
+
+void
+scanner_start (Scanner *scanner)
+{
+ GError *error = NULL;
+ scanner->priv->thread = g_thread_create ((GThreadFunc) scan_thread, scanner, TRUE, &error);
+ if (error) {
+ g_critical ("Unable to create thread: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+
+void
+scanner_redetect (Scanner *scanner)
+{
+ ScanRequest *request;
+
+ if (scanner->priv->redetect)
+ return;
+ scanner->priv->redetect = TRUE;
+
+ g_debug ("Requesting redetection of scan devices");
+
+ request = g_malloc0 (sizeof (ScanRequest));
+ request->type = REQUEST_REDETECT;
+ g_async_queue_push (scanner->priv->scan_queue, request);
+}
+
+
+gboolean
+scanner_is_scanning (Scanner *scanner)
+{
+ return scanner->priv->scanning;
+}
+
+
+void
+scanner_scan (Scanner *scanner, const char *device, ScanOptions *options)
+{
+ ScanRequest *request;
+ const gchar *type_string;
+
+ switch (options->type) {
+ case SCAN_SINGLE:
+ type_string = "SCAN_SINGLE";
+ break;
+ case SCAN_ADF_FRONT:
+ type_string = "SCAN_ADF_FRONT";
+ break;
+ case SCAN_ADF_BACK:
+ type_string = "SCAN_ADF_BACK";
+ break;
+ case SCAN_ADF_BOTH:
+ type_string = "SCAN_ADF_BOTH";
+ break;
+ default:
+ g_assert (FALSE);
+ return;
+ }
+
+ g_debug ("scanner_scan (\"%s\", %d, %s)", device ? device : "(null)", options->dpi, type_string);
+ request = g_malloc0 (sizeof (ScanRequest));
+ request->type = REQUEST_START_SCAN;
+ request->job = g_malloc0 (sizeof (ScanJob));
+ request->job->device = g_strdup (device);
+ request->job->dpi = options->dpi;
+ request->job->scan_mode = options->scan_mode;
+ request->job->depth = options->depth;
+ request->job->type = options->type;
+ request->job->page_width = options->paper_width;
+ request->job->page_height = options->paper_height;
+ g_async_queue_push (scanner->priv->scan_queue, request);
+}
+
+
+void
+scanner_cancel (Scanner *scanner)
+{
+ ScanRequest *request;
+
+ request = g_malloc0 (sizeof (ScanRequest));
+ request->type = REQUEST_CANCEL;
+ g_async_queue_push (scanner->priv->scan_queue, request);
+}
+
+
+void scanner_free (Scanner *scanner)
+{
+ ScanRequest *request;
+
+ g_debug ("Stopping scan thread");
+
+ request = g_malloc0 (sizeof (ScanRequest));
+ request->type = REQUEST_QUIT;
+ g_async_queue_push (scanner->priv->scan_queue, request);
+
+ if (scanner->priv->thread)
+ g_thread_join (scanner->priv->thread);
+
+ g_async_queue_unref (scanner->priv->scan_queue);
+ g_object_unref (scanner);
+
+ sane_exit ();
+ g_debug ("sane_exit ()");
+}
+
+
+static void
+scanner_class_init (ScannerClass *klass)
+{
+ signals[AUTHORIZE] =
+ g_signal_new ("authorize",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ScannerClass, authorize),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+ signals[UPDATE_DEVICES] =
+ g_signal_new ("update-devices",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ScannerClass, update_devices),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+ signals[EXPECT_PAGE] =
+ g_signal_new ("expect-page",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ScannerClass, expect_page),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[GOT_PAGE_INFO] =
+ g_signal_new ("got-page-info",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ScannerClass, got_page_info),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+ signals[GOT_LINE] =
+ g_signal_new ("got-line",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ScannerClass, got_line),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+ signals[SCAN_FAILED] =
+ g_signal_new ("scan-failed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ScannerClass, scan_failed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+ signals[PAGE_DONE] =
+ g_signal_new ("page-done",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ScannerClass, page_done),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[DOCUMENT_DONE] =
+ g_signal_new ("document-done",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ScannerClass, document_done),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ signals[SCANNING_CHANGED] =
+ g_signal_new ("scanning-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ScannerClass, scanning_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_type_class_add_private (klass, sizeof (ScannerPrivate));
+
+ scanners = g_hash_table_new (g_direct_hash, g_direct_equal);
+}
+
+
+static void
+scanner_init (Scanner *scanner)
+{
+ scanner->priv = G_TYPE_INSTANCE_GET_PRIVATE (scanner, SCANNER_TYPE, ScannerPrivate);
+ scanner->priv->scan_queue = g_async_queue_new ();
+ scanner->priv->authorize_queue = g_async_queue_new ();
+}