diff options
Diffstat (limited to 'src/simple-scan.c')
| -rw-r--r-- | src/simple-scan.c | 633 | 
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; +} | 
