summaryrefslogtreecommitdiff
path: root/src/simple-scan.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/simple-scan.c')
-rw-r--r--src/simple-scan.c633
1 files changed, 633 insertions, 0 deletions
diff --git a/src/simple-scan.c b/src/simple-scan.c
new file mode 100644
index 0000000..ab59299
--- /dev/null
+++ b/src/simple-scan.c
@@ -0,0 +1,633 @@
+/*
+ * 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 <stdio.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <unistd.h>
+#include <gudev/gudev.h>
+#include <dbus/dbus-glib.h>
+
+#include <sane/sane.h> // For SANE_STATUS_CANCELLED
+
+#include "ui.h"
+#include "scanner.h"
+#include "book.h"
+
+
+static const char *default_device = NULL;
+
+static GUdevClient *udev_client;
+
+static SimpleScan *ui;
+
+static Scanner *scanner;
+
+static Book *book;
+
+static GTimer *log_timer;
+
+static FILE *log_file;
+
+static gboolean debug = FALSE;
+
+
+static void
+update_scan_devices_cb (Scanner *scanner, GList *devices)
+{
+ ui_set_scan_devices (ui, devices);
+}
+
+
+static void
+authorize_cb (Scanner *scanner, const gchar *resource)
+{
+ gchar *username = NULL, *password = NULL;
+ ui_authorize (ui, resource, &username, &password);
+ scanner_authorize (scanner, username, password);
+ g_free (username);
+ g_free (password);
+}
+
+
+static Page *
+append_page ()
+{
+ Page *page;
+ Orientation orientation = TOP_TO_BOTTOM;
+ gboolean do_crop = FALSE;
+ gchar *named_crop = NULL;
+ gint width = 100, height = 100, dpi = 100, cx, cy, cw, ch;
+
+ /* Use current page if not used */
+ page = book_get_page (book, -1);
+ if (page && !page_has_data (page)) {
+ ui_set_selected_page (ui, page);
+ page_start (page);
+ return page;
+ }
+
+ /* Copy info from previous page */
+ if (page) {
+ orientation = page_get_orientation (page);
+ width = page_get_width (page);
+ height = page_get_height (page);
+ dpi = page_get_dpi (page);
+
+ do_crop = page_has_crop (page);
+ if (do_crop) {
+ named_crop = page_get_named_crop (page);
+ page_get_crop (page, &cx, &cy, &cw, &ch);
+ }
+ }
+
+ page = book_append_page (book, width, height, dpi, orientation);
+ if (do_crop) {
+ if (named_crop) {
+ page_set_named_crop (page, named_crop);
+ g_free (named_crop);
+ }
+ else
+ page_set_custom_crop (page, cw, ch);
+ page_move_crop (page, cx, cy);
+ }
+ ui_set_selected_page (ui, page);
+ page_start (page);
+
+ return page;
+}
+
+
+static void
+scanner_new_page_cb (Scanner *scanner)
+{
+ append_page ();
+}
+
+
+static gchar *
+get_profile_for_device (const gchar *current_device)
+{
+ gboolean ret;
+ DBusGConnection *connection;
+ DBusGProxy *proxy;
+ GError *error = NULL;
+ GType custom_g_type_string_string;
+ GPtrArray *profile_data_array = NULL;
+ gchar *device_id = NULL;
+ gchar *icc_profile = NULL;
+
+ /* Connect to the color manager on the session bus */
+ connection = dbus_g_bus_get (DBUS_BUS_SESSION, NULL);
+ proxy = dbus_g_proxy_new_for_name (connection,
+ "org.gnome.ColorManager",
+ "/org/gnome/ColorManager",
+ "org.gnome.ColorManager");
+
+ /* Get color profile */
+ device_id = g_strdup_printf ("sane:%s", current_device);
+ custom_g_type_string_string = dbus_g_type_get_collection ("GPtrArray",
+ dbus_g_type_get_struct("GValueArray",
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_INVALID));
+ ret = dbus_g_proxy_call (proxy, "GetProfilesForDevice", &error,
+ G_TYPE_STRING, device_id,
+ G_TYPE_STRING, "",
+ G_TYPE_INVALID,
+ custom_g_type_string_string, &profile_data_array,
+ G_TYPE_INVALID);
+ g_object_unref (proxy);
+ g_free (device_id);
+ if (!ret) {
+ g_debug ("The request failed: %s", error->message);
+ g_error_free (error);
+ return NULL;
+ }
+
+ if (profile_data_array->len > 0) {
+ GValueArray *gva;
+ GValue *gv = NULL;
+
+ /* Just use the preferred profile filename */
+ gva = (GValueArray *) g_ptr_array_index (profile_data_array, 0);
+ gv = g_value_array_get_nth (gva, 1);
+ icc_profile = g_value_dup_string (gv);
+ g_value_unset (gv);
+ }
+ else
+ g_debug ("There are no ICC profiles for the device sane:%s", current_device);
+ g_ptr_array_free (profile_data_array, TRUE);
+
+ return icc_profile;
+}
+
+
+static void
+scanner_page_info_cb (Scanner *scanner, ScanPageInfo *info)
+{
+ Page *page;
+
+ g_debug ("Page is %d pixels wide, %d pixels high, %d bits per pixel",
+ info->width, info->height, info->depth);
+
+ /* Add a new page */
+ page = append_page ();
+ page_set_scan_area (page, info->width, info->height, info->dpi);
+
+ /* Get ICC color profile */
+ /* FIXME: The ICC profile could change */
+ /* FIXME: Don't do a D-bus call for each page, cache color profiles */
+ page_set_color_profile (page, get_profile_for_device (info->device));
+}
+
+
+static void
+scanner_line_cb (Scanner *scanner, ScanLine *line)
+{
+ Page *page;
+
+ page = book_get_page (book, book_get_n_pages (book) - 1);
+ page_parse_scan_line (page, line);
+}
+
+
+static void
+scanner_page_done_cb (Scanner *scanner)
+{
+ Page *page;
+ page = book_get_page (book, book_get_n_pages (book) - 1);
+ page_finish (page);
+}
+
+
+static void
+remove_empty_page ()
+{
+ Page *page;
+
+ page = book_get_page (book, book_get_n_pages (book) - 1);
+
+ /* Remove a failed page */
+ if (page_has_data (page))
+ page_finish (page);
+ else
+ book_delete_page (book, page);
+}
+
+
+static void
+scanner_document_done_cb (Scanner *scanner)
+{
+ remove_empty_page ();
+}
+
+
+static void
+scanner_failed_cb (Scanner *scanner, GError *error)
+{
+ remove_empty_page ();
+ if (!g_error_matches (error, SCANNER_TYPE, SANE_STATUS_CANCELLED)) {
+ ui_show_error (ui,
+ /* Title of error dialog when scan failed */
+ _("Failed to scan"),
+ error->message,
+ TRUE);
+ }
+}
+
+
+static void
+scanner_scanning_changed_cb (Scanner *scanner)
+{
+ ui_set_scanning (ui, scanner_is_scanning (scanner));
+}
+
+
+static void
+scan_cb (SimpleScan *ui, const gchar *device, ScanOptions *options)
+{
+ /* Default filename to use when saving document (and extension will be added, e.g. .jpg) */
+ const gchar *filename_prefix = _("Scanned Document");
+ const gchar *extension;
+ gchar *filename;
+
+ if (options->scan_mode == SCAN_MODE_COLOR)
+ extension = "jpg";
+ else
+ extension = "pdf";
+
+ g_debug ("Requesting scan at %d dpi from device '%s'", options->dpi, device);
+
+ if (!scanner_is_scanning (scanner))
+ append_page ();
+
+ filename = g_strdup_printf ("%s.%s", filename_prefix, extension);
+ ui_set_default_file_name (ui, filename);
+ g_free (filename);
+ scanner_scan (scanner, device, options);
+}
+
+
+static void
+cancel_cb (SimpleScan *ui)
+{
+ scanner_cancel (scanner);
+}
+
+
+static gboolean
+save_book_by_extension (GFile *file, GError **error)
+{
+ gboolean result;
+ gchar *uri, *uri_lower;
+
+ uri = g_file_get_uri (file);
+ uri_lower = g_utf8_strdown (uri, -1);
+ if (g_str_has_suffix (uri_lower, ".pdf"))
+ result = book_save (book, "pdf", file, error);
+ else if (g_str_has_suffix (uri_lower, ".ps"))
+ result = book_save (book, "ps", file, error);
+ else if (g_str_has_suffix (uri_lower, ".png"))
+ result = book_save (book, "png", file, error);
+ else if (g_str_has_suffix (uri_lower, ".tif") || g_str_has_suffix (uri_lower, ".tiff"))
+ result = book_save (book, "tiff", file, error);
+ else
+ result = book_save (book, "jpeg", file, error);
+
+ g_free (uri);
+ g_free (uri_lower);
+
+ return result;
+}
+
+
+static void
+save_cb (SimpleScan *ui, const gchar *uri)
+{
+ GError *error = NULL;
+ GFile *file;
+
+ g_debug ("Saving to '%s'", uri);
+
+ file = g_file_new_for_uri (uri);
+ if (!save_book_by_extension (file, &error)) {
+ g_warning ("Error saving file: %s", error->message);
+ ui_show_error (ui,
+ /* Title of error dialog when save failed */
+ _("Failed to save file"),
+ error->message,
+ FALSE);
+ g_error_free (error);
+ }
+ g_object_unref (file);
+}
+
+
+static gchar *
+get_temporary_filename (const gchar *prefix, const gchar *extension)
+{
+ gint fd;
+ gchar *filename, *path;
+ GError *error = NULL;
+
+ /* NOTE: I'm not sure if this is a 100% safe strategy to use g_file_open_tmp(), close and
+ * use the filename but it appears to work in practise */
+
+ filename = g_strdup_printf ("%s-XXXXXX.%s", prefix, extension);
+ fd = g_file_open_tmp (filename, &path, &error);
+ g_free (filename);
+ if (fd < 0) {
+ g_warning ("Error saving email attachment: %s", error->message);
+ g_clear_error (&error);
+ return NULL;
+ }
+ close (fd);
+
+ return path;
+}
+
+
+static void
+email_cb (SimpleScan *ui, const gchar *profile)
+{
+ gboolean saved = FALSE;
+ GError *error = NULL;
+ GString *command_line;
+
+ command_line = g_string_new ("xdg-email");
+
+ /* Save text files as PDFs */
+ if (strcmp (profile, "text") == 0) {
+ gchar *path;
+
+ /* Open a temporary file */
+ path = get_temporary_filename ("scanned-document", "pdf");
+ if (path) {
+ GFile *file;
+
+ file = g_file_new_for_path (path);
+ saved = book_save (book, "pdf", file, &error);
+ g_string_append_printf (command_line, " --attach %s", path);
+ g_free (path);
+ g_object_unref (file);
+ }
+ }
+ else {
+ gint i;
+
+ for (i = 0; i < book_get_n_pages (book); i++) {
+ gchar *path;
+ GFile *file;
+
+ path = get_temporary_filename ("scanned-document", "jpg");
+ if (!path) {
+ saved = FALSE;
+ break;
+ }
+
+ file = g_file_new_for_path (path);
+ saved = page_save (book_get_page (book, i), "jpeg", file, &error);
+ g_string_append_printf (command_line, " --attach %s", path);
+ g_free (path);
+ g_object_unref (file);
+
+ if (!saved)
+ break;
+ }
+ }
+
+ if (saved) {
+ g_debug ("Launchind email client: %s", command_line->str);
+ g_spawn_command_line_async (command_line->str, &error);
+
+ if (error) {
+ g_warning ("Unable to start email: %s", error->message);
+ g_clear_error (&error);
+ }
+ }
+ else {
+ g_warning ("Unable to save email file: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ g_string_free (command_line, TRUE);
+}
+
+
+static void
+quit_cb (SimpleScan *ui)
+{
+ g_object_unref (book);
+ g_object_unref (ui);
+ g_object_unref (udev_client);
+ scanner_free (scanner);
+ gtk_main_quit ();
+}
+
+
+static void
+version()
+{
+ /* NOTE: Is not translated so can be easily parsed */
+ fprintf(stderr, "%1$s %2$s\n", SIMPLE_SCAN_BINARY, VERSION);
+}
+
+
+static void
+usage(int show_gtk)
+{
+ fprintf(stderr,
+ /* Description on how to use simple-scan displayed on command-line */
+ _("Usage:\n"
+ " %s [DEVICE...] - Scanning utility"), SIMPLE_SCAN_BINARY);
+
+ fprintf(stderr,
+ "\n\n");
+
+ fprintf(stderr,
+ /* Description on how to use simple-scan displayed on command-line */
+ _("Help Options:\n"
+ " -d, --debug Print debugging messages\n"
+ " -v, --version Show release version\n"
+ " -h, --help Show help options\n"
+ " --help-all Show all help options\n"
+ " --help-gtk Show GTK+ options"));
+ fprintf(stderr,
+ "\n\n");
+
+ if (show_gtk) {
+ fprintf(stderr,
+ /* Description on simple-scan command-line GTK+ options displayed on command-line */
+ _("GTK+ Options:\n"
+ " --class=CLASS Program class as used by the window manager\n"
+ " --name=NAME Program name as used by the window manager\n"
+ " --screen=SCREEN X screen to use\n"
+ " --sync Make X calls synchronous\n"
+ " --gtk-module=MODULES Load additional GTK+ modules\n"
+ " --g-fatal-warnings Make all warnings fatal"));
+ fprintf(stderr,
+ "\n\n");
+ }
+}
+
+
+static void
+log_cb (const gchar *log_domain, GLogLevelFlags log_level,
+ const gchar *message, gpointer data)
+{
+ /* Log everything to a file */
+ if (log_file) {
+ const gchar *prefix;
+
+ switch (log_level & G_LOG_LEVEL_MASK) {
+ case G_LOG_LEVEL_ERROR:
+ prefix = "ERROR:";
+ break;
+ case G_LOG_LEVEL_CRITICAL:
+ prefix = "CRITICAL:";
+ break;
+ case G_LOG_LEVEL_WARNING:
+ prefix = "WARNING:";
+ break;
+ case G_LOG_LEVEL_MESSAGE:
+ prefix = "MESSAGE:";
+ break;
+ case G_LOG_LEVEL_INFO:
+ prefix = "INFO:";
+ break;
+ case G_LOG_LEVEL_DEBUG:
+ prefix = "DEBUG:";
+ break;
+ default:
+ prefix = "LOG:";
+ break;
+ }
+
+ fprintf (log_file, "[%+.2fs] %s %s\n", g_timer_elapsed (log_timer, NULL), prefix, message);
+ }
+
+ /* Only show debug if requested */
+ if (log_level & G_LOG_LEVEL_DEBUG) {
+ if (debug)
+ g_log_default_handler (log_domain, log_level, message, data);
+ }
+ else
+ g_log_default_handler (log_domain, log_level, message, data);
+}
+
+
+static void
+get_options (int argc, char **argv)
+{
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ char *arg = argv[i];
+
+ if (strcmp (arg, "-d") == 0 ||
+ strcmp (arg, "--debug") == 0) {
+ debug = TRUE;
+ }
+ else if (strcmp (arg, "-v") == 0 ||
+ strcmp (arg, "--version") == 0) {
+ version ();
+ exit (0);
+ }
+ else if (strcmp (arg, "-h") == 0 ||
+ strcmp (arg, "--help") == 0) {
+ usage (FALSE);
+ exit (0);
+ }
+ else if (strcmp (arg, "--help-all") == 0 ||
+ strcmp (arg, "--help-gtk") == 0) {
+ usage (TRUE);
+ exit (0);
+ }
+ else {
+ if (default_device) {
+ fprintf (stderr, "Unknown argument: '%s'\n", arg);
+ exit (1);
+ }
+ default_device = arg;
+ }
+ }
+}
+
+
+static void
+on_uevent (GUdevClient *client, const gchar *action, GUdevDevice *device)
+{
+ scanner_redetect (scanner);
+}
+
+
+int
+main (int argc, char **argv)
+{
+ const char *udev_subsystems[] = { "usb", NULL };
+ gchar *path;
+
+ g_thread_init (NULL);
+
+ /* Log to a file */
+ log_timer = g_timer_new ();
+ path = g_build_filename (g_get_user_cache_dir (), "simple-scan", NULL);
+ g_mkdir_with_parents (path, 0700);
+ g_free (path);
+ path = g_build_filename (g_get_user_cache_dir (), "simple-scan", "simple-scan.log", NULL);
+ log_file = fopen (path, "w");
+ g_free (path);
+ g_log_set_default_handler (log_cb, NULL);
+
+ bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ gtk_init (&argc, &argv);
+
+ get_options (argc, argv);
+
+ g_debug ("Starting Simple Scan %s, PID=%i", VERSION, getpid ());
+
+ ui = ui_new ();
+ book = ui_get_book (ui);
+ g_signal_connect (ui, "start-scan", G_CALLBACK (scan_cb), NULL);
+ g_signal_connect (ui, "stop-scan", G_CALLBACK (cancel_cb), NULL);
+ g_signal_connect (ui, "save", G_CALLBACK (save_cb), NULL);
+ g_signal_connect (ui, "email", G_CALLBACK (email_cb), NULL);
+ g_signal_connect (ui, "quit", G_CALLBACK (quit_cb), NULL);
+
+ scanner = scanner_new ();
+ g_signal_connect (G_OBJECT (scanner), "update-devices", G_CALLBACK (update_scan_devices_cb), NULL);
+ g_signal_connect (G_OBJECT (scanner), "authorize", G_CALLBACK (authorize_cb), NULL);
+ g_signal_connect (G_OBJECT (scanner), "expect-page", G_CALLBACK (scanner_new_page_cb), NULL);
+ g_signal_connect (G_OBJECT (scanner), "got-page-info", G_CALLBACK (scanner_page_info_cb), NULL);
+ g_signal_connect (G_OBJECT (scanner), "got-line", G_CALLBACK (scanner_line_cb), NULL);
+ g_signal_connect (G_OBJECT (scanner), "page-done", G_CALLBACK (scanner_page_done_cb), NULL);
+ g_signal_connect (G_OBJECT (scanner), "document-done", G_CALLBACK (scanner_document_done_cb), NULL);
+ g_signal_connect (G_OBJECT (scanner), "scan-failed", G_CALLBACK (scanner_failed_cb), NULL);
+ g_signal_connect (G_OBJECT (scanner), "scanning-changed", G_CALLBACK (scanner_scanning_changed_cb), NULL);
+
+ udev_client = g_udev_client_new (udev_subsystems);
+ g_signal_connect (udev_client, "uevent", G_CALLBACK (on_uevent), NULL);
+
+ if (default_device)
+ ui_set_selected_device (ui, default_device);
+
+ ui_start (ui);
+ scanner_start (scanner);
+
+ gtk_main ();
+
+ return 0;
+}