From 7ab8c8e0307a0d58438619693c53d9844763f1e8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B6rg=20Frings-F=C3=BCrst?= <debian@jff.email>
Date: Sat, 12 Sep 2020 11:16:14 +0200
Subject: New upstream version 3.38.0

---
 src/app-window.ui    |   1 +
 src/app-window.vala  | 178 +++++++++++++++++++++++--------
 src/book.vala        |  26 ++---
 src/help-overlay.ui  |   9 +-
 src/page-view.vala   | 106 ++++++++++++++++---
 src/scanner.vala     |  80 +++++++-------
 src/simple-scan.vala | 294 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 7 files changed, 579 insertions(+), 115 deletions(-)

(limited to 'src')

diff --git a/src/app-window.ui b/src/app-window.ui
index e0e16c0..6f42a51 100644
--- a/src/app-window.ui
+++ b/src/app-window.ui
@@ -670,6 +670,7 @@
             <property name="can_focus">False</property>
             <property name="receives_default">False</property>
             <property name="use_underline">True</property>
+            <accelerator key="F10" signal="activate"/>
             <child>
               <object class="GtkImage">
                 <property name="visible">True</property>
diff --git a/src/app-window.vala b/src/app-window.vala
index 30adac9..67f10a8 100644
--- a/src/app-window.vala
+++ b/src/app-window.vala
@@ -188,6 +188,7 @@ public class AppWindow : Gtk.ApplicationWindow
 
     public signal void start_scan (string? device, ScanOptions options);
     public signal void stop_scan ();
+    public signal void redetect ();
 
     public AppWindow ()
     {
@@ -224,7 +225,7 @@ public class AppWindow : Gtk.ApplicationWindow
                                             Gtk.ButtonsType.NONE,
                                             "%s", error_title);
         dialog.add_button (_("_Close"), 0);
-        dialog.format_secondary_text ("%s", error_text);
+        dialog.format_secondary_markup  ("%s", error_text);
         dialog.run ();
         dialog.destroy ();
     }
@@ -245,7 +246,7 @@ public class AppWindow : Gtk.ApplicationWindow
 
     private void update_scan_status ()
     {
-        scan_button.set_sensitive(false);
+        scan_button.sensitive = false;
         if (!have_devices)
         {
             status_primary_label.set_text (/* Label shown when searching for scanners */
@@ -255,12 +256,13 @@ public class AppWindow : Gtk.ApplicationWindow
         }
         else if (get_selected_device () != null)
         {
-            scan_button.set_sensitive(true);
+            scan_button.sensitive = true;
             status_primary_label.set_text (/* Label shown when detected a scanner */
                                            _("Ready to Scan"));
             status_secondary_label.set_text (get_selected_device_label ());
             status_secondary_label.visible = false;
             device_combo.visible = true;
+            device_combo.sensitive = true;
         }
         else if (this.missing_driver != null)
         {
@@ -449,7 +451,7 @@ public class AppWindow : Gtk.ApplicationWindow
         directory = settings.get_string ("save-directory");
 
         if (directory == null || directory == "")
-            directory = Environment.get_user_special_dir (UserDirectory.DOCUMENTS);
+            directory = GLib.Filename.to_uri(Environment.get_user_special_dir (UserDirectory.DOCUMENTS));
 
         var save_dialog = new Gtk.FileChooserNative (/* Save dialog: Dialog title */
                                                      _("Save As…"),
@@ -458,12 +460,15 @@ public class AppWindow : Gtk.ApplicationWindow
                                                      _("_Save"),
                                                      _("_Cancel"));
         save_dialog.local_only = false;
+
+        var save_format = settings.get_string ("save-format");
         if (book_uri != null)
             save_dialog.set_uri (book_uri);
         else {
-            save_dialog.set_current_folder (directory);
-            /* Default filename to use when saving document */
-            save_dialog.set_current_name (_("Scanned Document.pdf"));
+            save_dialog.set_current_folder_uri (directory);
+            /* Default filename to use when saving document. */
+            /* To that filename the extension will be added, eg. "Scanned Document.pdf" */
+            save_dialog.set_current_name (_("Scanned Document") + "." + mime_type_to_extension (save_format));
         }
 
         /* Filter to only show images by default */
@@ -489,31 +494,32 @@ public class AppWindow : Gtk.ApplicationWindow
         file_type_store.set (iter,
                              /* Save dialog: Label for saving in PDF format */
                              0, _("PDF (multi-page document)"),
-                             1, ".pdf",
+                             1, "application/pdf",
                              -1);
         file_type_store.append (out iter);
         file_type_store.set (iter,
                              /* Save dialog: Label for saving in JPEG format */
                              0, _("JPEG (compressed)"),
-                             1, ".jpg",
+                             1, "image/jpeg",
                              -1);
         file_type_store.append (out iter);
         file_type_store.set (iter,
                              /* Save dialog: Label for saving in PNG format */
                              0, _("PNG (lossless)"),
-                             1, ".png",
+                             1, "image/png",
                              -1);
 #if HAVE_WEBP
         file_type_store.append (out iter);
         file_type_store.set (iter,
                              /* Save dialog: Label for sabing in WEBP format */
                              0, _("WebP (compressed)"),
-                             1, ".webp",
+                             1, "image/webp",
                              -1);
 #endif
 
         var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
         box.visible = true;
+        box.spacing = 10;
         save_dialog.set_extra_widget (box);
 
         /* Label in save dialog beside combo box to choose file format (PDF, JPEG, PNG, WEBP) */
@@ -528,29 +534,47 @@ public class AppWindow : Gtk.ApplicationWindow
         file_type_combo.add_attribute (renderer, "text", 0);
         box.add (file_type_combo);
 
+        if (file_type_store.get_iter_first (out iter))
+        {
+            do
+            {
+                string mime_type;
+                file_type_store.get (iter, 1, out mime_type, -1);
+                if (mime_type == save_format)
+                    file_type_combo.set_active_iter (iter);
+            } while (file_type_store.iter_next (ref iter));
+        }
+
         /* Label in save dialog beside compression slider */
         var quality_label = new Gtk.Label (_("Compression:"));
         box.add (quality_label);
 
         var quality_adjustment = new Gtk.Adjustment (75, 0, 100, 1, 10, 0);
         var quality_scale = new Gtk.Scale (Gtk.Orientation.HORIZONTAL, quality_adjustment);
-        quality_scale.width_request = 200;
+        quality_scale.width_request = 250;
         quality_scale.draw_value = false;
-        quality_scale.add_mark (0, Gtk.PositionType.BOTTOM, null);
+        var minimum_size_label = "<small>%s</small>".printf (_("Minimum size"));
+        quality_scale.add_mark (quality_adjustment.lower, Gtk.PositionType.BOTTOM, minimum_size_label);
         quality_scale.add_mark (75, Gtk.PositionType.BOTTOM, null);
         quality_scale.add_mark (90, Gtk.PositionType.BOTTOM, null);
-        quality_scale.add_mark (100, Gtk.PositionType.BOTTOM, null);
+        var full_detail_label = "<small>%s</small>".printf (_("Full detail"));
+        quality_scale.add_mark (quality_adjustment.upper, Gtk.PositionType.BOTTOM, full_detail_label);
         quality_adjustment.value = settings.get_int ("jpeg-quality");
         quality_adjustment.value_changed.connect (() => { settings.set_int ("jpeg-quality", (int) quality_adjustment.value); });
         box.add (quality_scale);
 
-        file_type_combo.set_active (0);
+        /* Quality not applicable to PNG */
+        quality_scale.visible = quality_label.visible = (save_format != "image/png");
+
         file_type_combo.changed.connect (() =>
         {
-            var extension = "";
+            var mime_type = "";
             Gtk.TreeIter i;
             if (file_type_combo.get_active_iter (out i))
-                file_type_store.get (i, 1, out extension, -1);
+            {
+                file_type_store.get (i, 1, out mime_type, -1);
+                settings.set_string ("save-format", mime_type);
+            }
 
             var filename = save_dialog.get_current_name ();
 
@@ -558,11 +582,11 @@ public class AppWindow : Gtk.ApplicationWindow
             var extension_index = filename.last_index_of_char ('.');
             if (extension_index >= 0)
                 filename = filename.slice (0, extension_index);
-            filename = filename + extension;
+            filename = filename + "." + mime_type_to_extension (mime_type);
             save_dialog.set_current_name (filename);
 
             /* Quality not applicable to PNG */
-            quality_scale.visible = quality_label.visible = (extension != ".png");
+            quality_scale.visible = quality_label.visible = (mime_type != "image/png");
         });
 
         while (true)
@@ -574,25 +598,20 @@ public class AppWindow : Gtk.ApplicationWindow
                 return null;
             }
 
-            var extension = "";
+            var mime_type = "";
             Gtk.TreeIter i;
             if (file_type_combo.get_active_iter (out i))
-                file_type_store.get (i, 1, out extension, -1);
+                file_type_store.get (i, 1, out mime_type, -1);
 
             var uri = save_dialog.get_uri ();
 
             var extension_index = uri.last_index_of_char ('.');
             if (extension_index < 0)
-                uri += extension;
+                uri += "." + mime_type_to_extension (mime_type);
 
             /* Check the file(s) don't already exist */
             var files = new List<File> ();
-            var format = uri_to_format (uri);
-#if HAVE_WEBP
-            if (format == "jpeg" || format == "png" || format == "webp")
-#else
-            if (format == "jpeg" || format == "png")
-#endif
+            if (mime_type == "image/jpeg" || mime_type == "image/png" || mime_type == "image/webp")
             {
                 for (var j = 0; j < book.n_pages; j++)
                     files.append (make_indexed_file (uri, j, book.n_pages));
@@ -602,7 +621,8 @@ public class AppWindow : Gtk.ApplicationWindow
 
             if (check_overwrite (save_dialog.transient_for, files))
             {
-                settings.set_string ("save-directory", save_dialog.get_current_folder ());
+                var directory_uri = uri.substring (0, uri.last_index_of ("/") + 1);
+                settings.set_string ("save-directory", directory_uri);
                 save_dialog.destroy ();
                 return uri;
             }
@@ -633,19 +653,47 @@ public class AppWindow : Gtk.ApplicationWindow
         return true;
     }
 
-    private string uri_to_format (string uri)
+    private string? mime_type_to_extension (string mime_type)
     {
-        var uri_lower = uri.down ();
-        if (uri_lower.has_suffix (".pdf"))
+        if (mime_type == "application/pdf")
             return "pdf";
-        else if (uri_lower.has_suffix (".png"))
+        else if (mime_type == "image/jpeg")
+            return "jpg";
+        else if (mime_type == "image/png")
             return "png";
-#if HAVE_WEBP
-        else if (uri_lower.has_suffix (".webp"))
+        else if (mime_type == "image/webp")
             return "webp";
-#endif
         else
-            return "jpeg";
+            return null;
+    }
+
+    private string? extension_to_mime_type (string extension)
+    {
+        var extension_lower = extension.down ();
+        if (extension_lower == "pdf")
+            return "application/pdf";
+        else if (extension_lower == "jpg")
+            return "image/jpeg";
+        else if (extension_lower == "png")
+            return "image/png";
+        else if (extension_lower == "webp")
+            return "image/webp";
+        else
+            return null;
+    }
+
+    private string uri_to_mime_type (string uri)
+    {
+        var extension_index = uri.last_index_of_char ('.');
+        if (extension_index < 0)
+            return "image/jpeg";
+        var extension = uri.substring (extension_index + 1);
+
+        var mime_type = extension_to_mime_type (extension);
+        if (mime_type == null)
+            return "image/jpeg";
+
+        return mime_type;
     }
 
     private async bool save_document_async ()
@@ -658,7 +706,7 @@ public class AppWindow : Gtk.ApplicationWindow
 
         debug ("Saving to '%s'", uri);
 
-        var format = uri_to_format (uri);
+        var mime_type = uri_to_mime_type (uri);
 
         var cancellable = new Cancellable ();
         var progress_bar =  new CancellableProgressBar (_("Saving"), cancellable);
@@ -667,7 +715,7 @@ public class AppWindow : Gtk.ApplicationWindow
         save_button.sensitive = false;
         try
         {
-            yield book.save_async (format, settings.get_int ("jpeg-quality"), file, (fraction) =>
+            yield book.save_async (mime_type, settings.get_int ("jpeg-quality"), file, (fraction) =>
             {
                 progress_bar.set_fraction (fraction);
             }, cancellable);
@@ -748,6 +796,9 @@ public class AppWindow : Gtk.ApplicationWindow
             if (scanning)
                 stop_scan ();
 
+            have_devices = false;
+            /* Refresh list of devices to detect network scanners, and fix issues with disconnected scanners */
+            redetect ();
             clear_document ();
         });
     }
@@ -773,6 +824,7 @@ public class AppWindow : Gtk.ApplicationWindow
     {
         status_primary_label.set_text (/* Label shown when scan started */
                                        _("Contacting scanner…"));
+        device_combo.sensitive = false;
         start_scan (get_selected_device (), options);
     }
 
@@ -1428,11 +1480,21 @@ public class AppWindow : Gtk.ApplicationWindow
         try
         {
             var dir = DirUtils.make_tmp ("simple-scan-XXXXXX");
-            var type = document_hint == "text" ? "pdf" : "jpeg";
-            var file = File.new_for_path (Path.build_filename (dir, "scan." + type));
-            yield book.save_async (type, settings.get_int ("jpeg-quality"), file, null, null);
+            string mime_type, filename;
+            if (document_hint == "text")
+            {
+                mime_type = "application/pdf";
+                filename = "scan.pdf";
+            }
+            else
+            {
+                mime_type = "image/jpeg";
+                filename = "scan.jpg";
+            }
+            var file = File.new_for_path (Path.build_filename (dir, filename));
+            yield book.save_async (mime_type, settings.get_int ("jpeg-quality"), file, null, null);
             var command_line = "xdg-email";
-            if (type == "pdf")
+            if (mime_type == "application/pdf")
                 command_line += " --attach %s".printf (file.get_path ());
             else
             {
@@ -1574,16 +1636,31 @@ public class AppWindow : Gtk.ApplicationWindow
             /* Instructions on how to install Brother scanner drivers */
             instructions = _("Drivers for this are available on the <a href=\"http://support.brother.com\">Brother website</a>.");
             break;
+        case "pixma":
+            /* Message to indicate a Canon Pixma scanner has been detected */
+            message = _("You appear to have a Canon scanner, which is supported by the <a href=\"http://www.sane-project.org/man/sane-pixma.5.html\">Pixma SANE backend</a>.");
+            /* Instructions on how to resolve issue with SANE scanner drivers */
+            instructions = _("Please check if your <a href=\"http://www.sane-project.org/sane-supported-devices.html\">scanner is supported by SANE</a>, otherwise report the issue to the <a href=\"https://alioth-lists.debian.net/cgi-bin/mailman/listinfo/sane-devel\">SANE mailing list</a>.");
+            break;
         case "samsung":
             /* Message to indicate a Samsung scanner has been detected */
             message = _("You appear to have a Samsung scanner.");
-            /* Instructions on how to install Samsung scanner drivers */
-            instructions = _("Drivers for this are available on the <a href=\"http://samsung.com/support\">Samsung website</a>.");
+            /* Instructions on how to install Samsung scanner drivers.
+               Because HP acquired Samsung's global printing business in 2017, the support is made on HP site. */
+            instructions = _("Drivers for this are available on the <a href=\"https://support.hp.com\">HP website</a> (HP acquired Samsung's printing business).");
             break;
         case "hpaio":
+        case "smfp":
             /* Message to indicate a HP scanner has been detected */
             message = _("You appear to have an HP scanner.");
-            packages_to_install = { "libsane-hpaio" };
+            if (missing_driver == "hpaio")
+                packages_to_install = { "libsane-hpaio" };
+            else
+                /* Instructions on how to install HP scanner drivers.
+                   smfp is rebranded and slightly modified Samsung devices,
+                   for example: HP Laser MFP 135a is rebranded Samsung Xpress SL-M2070.
+                   It require custom drivers, not available in hpaio package */
+                instructions = _("Drivers for this are available on the <a href=\"https://support.hp.com\">HP website</a>.");
             break;
         case "epkowa":
             /* Message to indicate an Epson scanner has been detected */
@@ -1591,6 +1668,12 @@ public class AppWindow : Gtk.ApplicationWindow
             /* Instructions on how to install Epson scanner drivers */
             instructions = _("Drivers for this are available on the <a href=\"http://support.epson.com\">Epson website</a>.");
             break;
+        case "lexmark_nscan":
+            /* Message to indicate an Lexmark scanner has been detected */
+            message = _("You appear to have an Lexmark scanner.");
+            /* Instructions on how to install Lexmark scanner drivers */
+            instructions = _("Drivers for this are available on the <a href=\"http://support.lexmark.com\">Lexmark website</a>.");
+            break;
         }
         var dialog = new Gtk.Dialog.with_buttons (/* Title of dialog giving instructions on how to install drivers */
                                                   _("Install drivers"), this, Gtk.DialogFlags.MODAL, _("_Close"), Gtk.ResponseType.CLOSE);
@@ -1601,6 +1684,7 @@ public class AppWindow : Gtk.ApplicationWindow
         label.visible = true;
         label.xalign = 0f;
         label.vexpand = true;
+        label.use_markup = true;
         dialog.get_content_area ().add (label);
 
         var instructions_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
@@ -1780,7 +1864,7 @@ public class AppWindow : Gtk.ApplicationWindow
         app.set_accels_for_action ("app.print", { "<Ctrl>P" });
         app.set_accels_for_action ("app.help", { "F1" });
         app.set_accels_for_action ("app.quit", { "<Ctrl>Q" });
-        app.set_accels_for_action ("win.show-help-overlay", { "<Ctrl>F1" });
+        app.set_accels_for_action ("win.show-help-overlay", { "<Ctrl>question" });
 
         var gear_menu = new Menu ();
         var section = new Menu ();
diff --git a/src/book.vala b/src/book.vala
index 6db2952..798fe98 100644
--- a/src/book.vala
+++ b/src/book.vala
@@ -136,10 +136,10 @@ public class Book : Object
         return pages.index (page);
     }
 
-    public async void save_async (string t, int q, File f, ProgressionCallback? p, Cancellable? c) throws Error
+    public async void save_async (string mime_type, int quality, File file, ProgressionCallback? progress_cb, Cancellable? cancellable = null) throws Error
     {
         var book_saver = new BookSaver ();
-        yield book_saver.save_async (this, t, q, f, p, c);
+        yield book_saver.save_async (this, mime_type, quality, file, progress_cb, cancellable);
     }
 }
 
@@ -160,7 +160,7 @@ private class BookSaver
      * distributes all encode tasks to other threads then yield so
      * the ui can continue operating. The method then return once saving
      * is completed, cancelled, or failed */
-    public async void save_async (Book book, string type, int quality, File file, ProgressionCallback? progression_callback, Cancellable? cancellable) throws Error
+    public async void save_async (Book book, string mime_type, int quality, File file, ProgressionCallback? progression_callback, Cancellable? cancellable) throws Error
     {
         var timer = new Timer ();
 
@@ -184,20 +184,20 @@ private class BookSaver
 
         /* Configure an encoder */
         ThreadPoolFunc<EncodeTask>? encode_delegate = null;
-        switch (type)
+        switch (mime_type)
         {
-        case "jpeg":
+        case "image/jpeg":
             encode_delegate = encode_jpeg;
             break;
-        case "png":
+        case "image/png":
             encode_delegate = encode_png;
             break;
 #if HAVE_WEBP
-        case "webp":
+        case "image/webp":
             encode_delegate = encode_webp;
             break;
 #endif
-        case "pdf":
+        case "application/pdf":
             encode_delegate = encode_pdf;
             break;
         }
@@ -205,16 +205,16 @@ private class BookSaver
 
         /* Configure a writer */
         ThreadFunc<Error?>? write_delegate = null;
-        switch (type)
+        switch (mime_type)
         {
-        case "jpeg":
-        case "png":
+        case "image/jpeg":
+        case "image/png":
 #if HAVE_WEBP
-        case "webp":
+        case "image/webp":
 #endif
             write_delegate = write_multifile;
             break;
-        case "pdf":
+        case "application/pdf":
             write_delegate = write_pdf;
             break;
         }
diff --git a/src/help-overlay.ui b/src/help-overlay.ui
index b1a0127..105d876 100644
--- a/src/help-overlay.ui
+++ b/src/help-overlay.ui
@@ -136,7 +136,14 @@
             <child>
               <object class="GtkShortcutsShortcut">
                 <property name="visible">1</property>
-                <property name="accelerator">&lt;ctrl&gt;F1</property>
+                <property name="accelerator">F10</property>
+                <property name="title" translatable="yes" context="shortcut window">Open menu</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkShortcutsShortcut">
+                <property name="visible">1</property>
+                <property name="accelerator">&lt;ctrl&gt;question</property>
                 <property name="title" translatable="yes" context="shortcut window">Keyboard shortcuts</property>
               </object>
             </child>
diff --git a/src/page-view.vala b/src/page-view.vala
index 91a2c82..90a8071 100644
--- a/src/page-view.vala
+++ b/src/page-view.vala
@@ -45,7 +45,9 @@ public class PageView : Object
         }
     }
 
-    private int border_width = 1;
+    private int ruler_width = 8;
+
+    private int border_width = 2;
 
     /* True if image needs to be regenerated */
     private bool update_image = true;
@@ -508,12 +510,12 @@ public class PageView : Object
 
     private int get_preview_width ()
     {
-        return width_ - border_width * 2;
+        return width_ - (border_width + ruler_width) * 2;
     }
 
     private int get_preview_height ()
     {
-        return height_ - border_width * 2;
+        return height_ - (border_width + ruler_width) * 2;
     }
 
     private void update_page_view ()
@@ -569,10 +571,10 @@ public class PageView : Object
         var cy = page.crop_y;
         var cw = page.crop_width;
         var ch = page.crop_height;
-        var dx = page_to_screen_x (cx);
-        var dy = page_to_screen_y (cy);
-        var dw = page_to_screen_x (cw);
-        var dh = page_to_screen_y (ch);
+        var dx = page_to_screen_x (cx) + border_width + ruler_width;
+        var dy = page_to_screen_y (cy) + border_width + ruler_width;
+        var dw = page_to_screen_x (cw) + border_width + ruler_width;
+        var dh = page_to_screen_y (ch) + border_width + ruler_width;
         var ix = x - dx;
         var iy = y - dy;
 
@@ -633,7 +635,7 @@ public class PageView : Object
             selected_crop_x = page.crop_x;
             selected_crop_y = page.crop_y;
             selected_crop_w = page.crop_width;
-            selected_crop_h = page.crop_height;;
+            selected_crop_h = page.crop_height;
         }
     }
 
@@ -831,19 +833,79 @@ public class PageView : Object
         context.set_line_width (1);
         context.translate (x_offset, y_offset);
 
+        /* Draw image */
+        context.translate (border_width + ruler_width, border_width + ruler_width);
+        Gdk.cairo_set_source_pixbuf (context, image, 0, 0);
+        context.paint ();
+
         /* Draw page border */
         context.set_source_rgb (0, 0, 0);
         context.set_line_width (border_width);
-        context.rectangle ((double)border_width / 2,
-                           (double)border_width / 2,
-                           width_ - border_width,
-                           height_ - border_width);
+
+        context.rectangle (0,
+                           0.0,
+                           w,
+                           h);
         context.stroke ();
 
-        /* Draw image */
-        context.translate (border_width, border_width);
-        Gdk.cairo_set_source_pixbuf (context, image, 0, 0);
-        context.paint ();
+        /* Draw horizontal ruler */
+        context.set_line_width (1);
+        var ruler_tick = 0;
+        var line = 0.0;
+        var big_ruler_tick = 5;
+
+        while (ruler_tick <= page.width)
+        {
+            line = page_to_screen_x (ruler_tick) + 0.5;
+            if (big_ruler_tick == 5)
+            {
+                context.move_to (line, 0);
+                context.line_to (line, -ruler_width);
+                context.move_to (line, h);
+                context.line_to (line, h + ruler_width);
+                big_ruler_tick = 0;
+            }
+            else
+            {
+                context.move_to (line, -2);
+                context.line_to (line, -5);
+                context.move_to (line, h + 2);
+                context.line_to (line, h + 5);
+            }
+            ruler_tick = ruler_tick + page.dpi/5;
+            big_ruler_tick = big_ruler_tick + 1;
+        }
+        context.stroke ();
+
+        /* Draw vertical ruler */
+        ruler_tick = 0;
+        line = 0.0;
+        big_ruler_tick = 5;
+        while (ruler_tick <= page.height)
+        {
+            line = page_to_screen_y (ruler_tick) + 0.5;
+
+            if (big_ruler_tick == 5)
+            {
+                context.move_to (0, line);
+                context.line_to (-ruler_width, line);
+
+                context.move_to (w, line);
+                context.line_to (w + ruler_width, line);
+                big_ruler_tick = 0;
+            }
+            else
+            {
+                context.move_to (-2, line);
+                context.line_to (-5, line);
+
+                context.move_to (w + 2, line);
+                context.line_to (w + 5, line);
+            }
+            ruler_tick = ruler_tick + page.dpi/5;
+            big_ruler_tick = big_ruler_tick + 1;
+        }
+        context.stroke ();
 
         /* Draw scan line */
         if (page.is_scanning && page.scan_line > 0)
@@ -907,9 +969,19 @@ public class PageView : Object
             context.fill ();
 
             /* Show new edge */
-            context.rectangle (dx - 1.5, dy - 1.5, dw + 3, dh + 3);
             context.set_source_rgb (1.0, 1.0, 1.0);
+            context.move_to (-border_width, dy - 1.5);
+            context.line_to (border_width + w, dy - 1.5);
+            context.move_to (-border_width, dy + dh + 1.5);
+            context.line_to (border_width + w, dy + dh + 1.5);
+            context.stroke ();
+
+            context.move_to (dx - 1.5, -border_width);
+            context.line_to (dx - 1.5, border_width + h);
+            context.move_to (dx + dw + 1.5, -border_width);
+            context.line_to (dx + dw + 1.5, border_width + h);
             context.stroke ();
+
             context.rectangle (dx - 0.5, dy - 0.5, dw + 1, dh + 1);
             context.set_source_rgb (0.0, 0.0, 0.0);
             context.stroke ();
diff --git a/src/scanner.vala b/src/scanner.vala
index dc58f14..becb9fa 100644
--- a/src/scanner.vala
+++ b/src/scanner.vala
@@ -429,7 +429,7 @@ public class Scanner : Object
             return false;
 
         var status = Sane.control_option (handle, option_index, Sane.Action.SET_AUTO, null, null);
-        debug ("sane_control_option (%d, SANE_ACTION_SET_AUTO) -> %s", (int) option_index, Sane.status_to_string (status));
+        debug ("sane_control_option (%d, SANE_ACTION_SET_AUTO, %s=auto) -> %s", (int) option_index, option.name, Sane.status_to_string (status));
         if (status != Sane.Status.GOOD)
             warning ("Error setting default option %s: %s", option.name, Sane.strstatus(status));
 
@@ -443,7 +443,7 @@ public class Scanner : Object
         Sane.Bool v = (Sane.Bool) value;
         var status = Sane.control_option (handle, option_index, Sane.Action.SET_VALUE, &v, null);
         result = (bool) v;
-        debug ("sane_control_option (%d, SANE_ACTION_SET_VALUE, %s) -> (%s, %s)", (int) option_index, value ? "SANE_TRUE" : "SANE_FALSE", Sane.status_to_string (status), result ? "SANE_TRUE" : "SANE_FALSE");
+        debug ("sane_control_option (%d, SANE_ACTION_SET_VALUE, %s=%s) -> (%s, %s)", (int) option_index, option.name, value ? "SANE_TRUE" : "SANE_FALSE", Sane.status_to_string (status), result ? "SANE_TRUE" : "SANE_FALSE");
     }
 
     private void set_int_option (Sane.Handle handle, Sane.OptionDescriptor option, Sane.Int option_index, int value, out int result)
@@ -480,7 +480,7 @@ public class Scanner : Object
         }
 
         var status = Sane.control_option (handle, option_index, Sane.Action.SET_VALUE, &v, null);
-        debug ("sane_control_option (%d, SANE_ACTION_SET_VALUE, %d) -> (%s, %d)", (int) option_index, value, Sane.status_to_string (status), (int) v);
+        debug ("sane_control_option (%d, SANE_ACTION_SET_VALUE, %s=%d) -> (%s, %d)", (int) option_index, option.name, value, Sane.status_to_string (status), (int) v);
         result = (int) v;
     }
 
@@ -520,7 +520,7 @@ public class Scanner : Object
 
         v_fixed = Sane.FIX (v);
         var status = Sane.control_option (handle, option_index, Sane.Action.SET_VALUE, &v_fixed, null);
-        debug ("sane_control_option (%d, SANE_ACTION_SET_VALUE, %f) -> (%s, %f)", (int) option_index, value, Sane.status_to_string (status), Sane.UNFIX (v_fixed));
+        debug ("sane_control_option (%d, SANE_ACTION_SET_VALUE, %s=%f) -> (%s, %f)", (int) option_index, option.name, value, Sane.status_to_string (status), Sane.UNFIX (v_fixed));
 
         result = Sane.UNFIX (v_fixed);
     }
@@ -550,9 +550,9 @@ public class Scanner : Object
         var status = Sane.control_option (handle, option_index, Sane.Action.SET_VALUE, &option.range.max, null);
 
         if (option.type == Sane.ValueType.FIXED)
-            debug ("sane_control_option (%d, SANE_ACTION_SET_VALUE, option.range.max=%f) -> (%s)", (int) option_index, Sane.UNFIX (option.range.max), Sane.status_to_string (status));
+            debug ("sane_control_option (%d, SANE_ACTION_SET_VALUE, %s=option.range.max=%f) -> (%s)", (int) option_index, option.name, Sane.UNFIX (option.range.max), Sane.status_to_string (status));
         else
-            debug ("sane_control_option (%d, SANE_ACTION_SET_VALUE, option.range.max=%d) -> (%s)", (int) option_index, (int) option.range.max, Sane.status_to_string (status));
+            debug ("sane_control_option (%d, SANE_ACTION_SET_VALUE, %s=option.range.max=%d) -> (%s)", (int) option_index, option.name, (int) option.range.max, Sane.status_to_string (status));
     }
 
     private bool set_string_option (Sane.Handle handle, Sane.OptionDescriptor option, Sane.Int option_index, string value, out string result)
@@ -568,7 +568,7 @@ public class Scanner : Object
         s[i] = '\0';
         var status = Sane.control_option (handle, option_index, Sane.Action.SET_VALUE, s, null);
         result = (string) s;
-        debug ("sane_control_option (%d, SANE_ACTION_SET_VALUE, \"%s\") -> (%s, \"%s\")", (int) option_index, value, Sane.status_to_string (status), result);
+        debug ("sane_control_option (%d, SANE_ACTION_SET_VALUE, %s=\"%s\") -> (%s, \"%s\")", (int) option_index, option.name, value, Sane.status_to_string (status), result);
 
         return status == Sane.Status.GOOD;
     }
@@ -918,7 +918,6 @@ public class Scanner : Object
             option = get_option_by_name (handle, Sane.NAME_SCAN_SOURCE, out index);
             if (option == null)
             {
-                 debug ("SCAN_SOURCE not available, trying alternative \"doc-source\"");
                  option = get_option_by_name (handle, "doc-source", out index); /* Samsung unified driver. LP: #892915 */
             }
             if (option != null)
@@ -932,7 +931,7 @@ public class Scanner : Object
                     "FlatBed",
                     "Normal",
                     Sane.I18N ("Normal"),
-                    "Document Table" /* Epson scanners, eg. ET-3760 */ 
+                    "Document Table" /* Epson scanners, eg. ET-3760 */
                 };
 
                 string[] adf_sources =
@@ -1135,7 +1134,12 @@ public class Scanner : Object
                 else
                     set_option_to_max (handle, option, index);
             }
-
+            if (job.page_width == 0) /* #90 Fix automatic mode for Epson scanners */
+            {
+                option = get_option_by_name (handle, "scan-area", out index);
+                if (option != null)
+                    set_string_option (handle, option, index, "Maximum", null);
+            }
             /* Set page size */
             option = get_option_by_name (handle, Sane.NAME_PAGE_WIDTH, out index);
             if (option != null && job.page_width > 0.0)
@@ -1147,40 +1151,34 @@ public class Scanner : Object
             option = get_option_by_name (handle, Sane.NAME_BRIGHTNESS, out index);
             if (option != null)
             {
-                if (job.brightness != 0)
+                if (option.type == Sane.ValueType.FIXED)
                 {
-                    if (option.type == Sane.ValueType.FIXED)
-                    {
-                        var brightness = scale_fixed (-100, 100, option, job.brightness);
-                        set_fixed_option (handle, option, index, brightness, null);
-                    }
-                    else if (option.type == Sane.ValueType.INT)
-                    {
-                        var brightness = scale_int (-100, 100, option, job.brightness);
-                        set_int_option (handle, option, index, brightness, null);
-                    }
-                    else
-                        warning ("Unable to set brightness, please file a bug");
+                    var brightness = scale_fixed (-100, 100, option, job.brightness);
+                    set_fixed_option (handle, option, index, brightness, null);
                 }
+                else if (option.type == Sane.ValueType.INT)
+                {
+                    var brightness = scale_int (-100, 100, option, job.brightness);
+                    set_int_option (handle, option, index, brightness, null);
+                }
+                else
+                    warning ("Unable to set brightness, please file a bug");
             }
             option = get_option_by_name (handle, Sane.NAME_CONTRAST, out index);
             if (option != null)
             {
-                if (job.contrast != 0)
+                if (option.type == Sane.ValueType.FIXED)
                 {
-                    if (option.type == Sane.ValueType.FIXED)
-                    {
-                        var contrast = scale_fixed (-100, 100, option, job.contrast);
-                        set_fixed_option (handle, option, index, contrast, null);
-                    }
-                    else if (option.type == Sane.ValueType.INT)
-                    {
-                        var contrast = scale_int (-100, 100, option, job.contrast);
-                        set_int_option (handle, option, index, contrast, null);
-                    }
-                    else
-                        warning ("Unable to set contrast, please file a bug");
+                    var contrast = scale_fixed (-100, 100, option, job.contrast);
+                    set_fixed_option (handle, option, index, contrast, null);
+                }
+                else if (option.type == Sane.ValueType.INT)
+                {
+                    var contrast = scale_int (-100, 100, option, job.contrast);
+                    set_int_option (handle, option, index, contrast, null);
                 }
+                else
+                    warning ("Unable to set contrast, please file a bug");
             }
 
             /* Test scanner options (hoping will not effect other scanners...) */
@@ -1294,6 +1292,16 @@ public class Scanner : Object
                     /* Error displayed when no documents at the start of scanning */
                     _("Document feeder empty"));
         }
+        else if (status == Sane.Status.NO_MEM)
+        {
+            fail_scan (status,
+                /* Out of memory error message with help instruction.
+                   Message written in Pango text markup language,
+                   A carriage return makes a line break, <tt> tag makes a monospace font */
+                _("Insufficient memory to perform scan.\n" +
+                  "Try to decrease <tt>Resolution</tt> or <tt>Page Size</tt> in <tt>Preferences</tt> menu. " +
+                  "For some scanners when scanning in high resolution, the scan size is restricted."));
+        }
         else if (status == Sane.Status.DEVICE_BUSY)
         {
             /* If device is busy don't interrupt, but keep waiting for scanner */
diff --git a/src/simple-scan.vala b/src/simple-scan.vala
index 771dc82..3f495e5 100644
--- a/src/simple-scan.vala
+++ b/src/simple-scan.vala
@@ -54,6 +54,7 @@ public class SimpleScan : Gtk.Application
         book = app.book;
         app.start_scan.connect (scan_cb);
         app.stop_scan.connect (cancel_cb);
+        app.redetect.connect (redetect_cb);
 
         scanner = Scanner.get_instance ();
         scanner.update_devices.connect (update_scan_devices_cb);
@@ -542,6 +543,217 @@ public class SimpleScan : Gtk.Application
       0x04f960a9, /* ADS-1600W */
     };
 
+    /* Taken from backend/pixma/pixma_mp150.c pixma_mp730.c pixma_mp750.c pixma_mp800.c in the pixma SANE backend repository */
+    /* Canon Pixma IDs extracted using the following Python script
+      import sys
+      for f in sys.argv:
+        for l in open(f, "r").readlines():
+          tokens=l.split ()
+          if len (tokens) >= 3 and tokens[0].startswith("#define") and tokens[1].endswith("_PID") and tokens[2].startswith("0x") and not tokens[2].endswith("ffff"):
+            print ( "0x04a9" + tokens[2][2:] + ", /* " +  tokens[1][:-4] + " * /")
+    */
+    private const uint32 pixma_devices[] = {
+      0x04a91709, /* MP150 */
+      0x04a9170a, /* MP170 */
+      0x04a9170b, /* MP450 */
+      0x04a9170c, /* MP500 */
+      0x04a91712, /* MP530 */
+      0x04a91714, /* MP160 */
+      0x04a91715, /* MP180 */
+      0x04a91716, /* MP460 */
+      0x04a91717, /* MP510 */
+      0x04a91718, /* MP600 */
+      0x04a91719, /* MP600R */
+      0x04a9172b, /* MP140 */
+      0x04a9171c, /* MX7600 */
+      0x04a91721, /* MP210 */
+      0x04a91722, /* MP220 */
+      0x04a91723, /* MP470 */
+      0x04a91724, /* MP520 */
+      0x04a91725, /* MP610 */
+      0x04a91727, /* MX300 */
+      0x04a91728, /* MX310 */
+      0x04a91729, /* MX700 */
+      0x04a9172c, /* MX850 */
+      0x04a9172e, /* MP630 */
+      0x04a9172f, /* MP620 */
+      0x04a91730, /* MP540 */
+      0x04a91731, /* MP480 */
+      0x04a91732, /* MP240 */
+      0x04a91733, /* MP260 */
+      0x04a91734, /* MP190 */
+      0x04a91735, /* MX860 */
+      0x04a91736, /* MX320 */
+      0x04a91737, /* MX330 */
+      0x04a9173a, /* MP250 */
+      0x04a9173b, /* MP270 */
+      0x04a9173c, /* MP490 */
+      0x04a9173d, /* MP550 */
+      0x04a9173e, /* MP560 */
+      0x04a9173f, /* MP640 */
+      0x04a91741, /* MX340 */
+      0x04a91742, /* MX350 */
+      0x04a91743, /* MX870 */
+      0x04a91746, /* MP280 */
+      0x04a91747, /* MP495 */
+      0x04a91748, /* MG5100 */
+      0x04a91749, /* MG5200 */
+      0x04a9174a, /* MG6100 */
+      0x04a9174d, /* MX360 */
+      0x04a9174e, /* MX410 */
+      0x04a9174f, /* MX420 */
+      0x04a91750, /* MX880 */
+      0x04a91751, /* MG2100 */
+      0x04a91752, /* MG3100 */
+      0x04a91753, /* MG4100 */
+      0x04a91754, /* MG5300 */
+      0x04a91755, /* MG6200 */
+      0x04a91757, /* MP493 */
+      0x04a91758, /* E500 */
+      0x04a91759, /* MX370 */
+      0x04a9175B, /* MX430 */
+      0x04a9175C, /* MX510 */
+      0x04a9175D, /* MX710 */
+      0x04a9175E, /* MX890 */
+      0x04a9175A, /* E600 */
+      0x04a91763, /* MG4200 */
+      0x04a9175F, /* MP230 */
+      0x04a91765, /* MG6300 */
+      0x04a91760, /* MG2200 */
+      0x04a91761, /* E510 */
+      0x04a91762, /* MG3200 */
+      0x04a91764, /* MG5400 */
+      0x04a91766, /* MX390 */
+      0x04a91767, /* E610 */
+      0x04a91768, /* MX450 */
+      0x04a91769, /* MX520 */
+      0x04a9176a, /* MX720 */
+      0x04a9176b, /* MX920 */
+      0x04a9176c, /* MG2400 */
+      0x04a9176d, /* MG2500 */
+      0x04a9176e, /* MG3500 */
+      0x04a9176f, /* MG6500 */
+      0x04a91770, /* MG6400 */
+      0x04a91771, /* MG5500 */
+      0x04a91772, /* MG7100 */
+      0x04a91774, /* MX470 */
+      0x04a91775, /* MX530 */
+      0x04a91776, /* MB5000 */
+      0x04a91777, /* MB5300 */
+      0x04a91778, /* MB2000 */
+      0x04a91779, /* MB2300 */
+      0x04a9177a, /* E400 */
+      0x04a9177b, /* E560 */
+      0x04a9177c, /* MG7500 */
+      0x04a9177e, /* MG6600 */
+      0x04a9177f, /* MG5600 */
+      0x04a91780, /* MG2900 */
+      0x04a91788, /* E460 */
+      0x04a91787, /* MX490 */
+      0x04a91789, /* E480 */
+      0x04a9178a, /* MG3600 */
+      0x04a9178b, /* MG7700 */
+      0x04a9178c, /* MG6900 */
+      0x04a9178d, /* MG6800 */
+      0x04a9178e, /* MG5700 */
+      0x04a91792, /* MB2700 */
+      0x04a91793, /* MB2100 */
+      0x04a91794, /* G3000 */
+      0x04a91795, /* G2000 */
+      0x04a9179f, /* TS9000 */
+      0x04a91800, /* TS8000 */
+      0x04a91801, /* TS6000 */
+      0x04a91802, /* TS5000 */
+      0x04a9180b, /* MG3000 */
+      0x04a9180c, /* E470 */
+      0x04a9181e, /* E410 */
+      0x04a9181d, /* G4000 */
+      0x04a91822, /* TS6100 */
+      0x04a91825, /* TS5100 */
+      0x04a91827, /* TS3100 */
+      0x04a91828, /* E3100 */
+      0x04a9178f, /* MB5400 */
+      0x04a91790, /* MB5100 */
+      0x04a91820, /* TS9100 */
+      0x04a91823, /* TR8500 */
+      0x04a91824, /* TR7500 */
+      0x04a9185c, /* TS9500 */
+      0x04a91912, /* LIDE400 */
+      0x04a91913, /* LIDE300 */
+      0x04a91821, /* TS8100 */
+      0x04a9183a, /* G2010 */
+      0x04a9183b, /* G3010 */
+      0x04a9183d, /* G4010 */
+      0x04a9183e, /* TS9180 */
+      0x04a9183f, /* TS8180 */
+      0x04a91840, /* TS6180 */
+      0x04a91841, /* TR8580 */
+      0x04a91842, /* TS8130 */
+      0x04a91843, /* TS6130 */
+      0x04a91844, /* TR8530 */
+      0x04a91845, /* TR7530 */
+      0x04a91846, /* XK50 */
+      0x04a91847, /* XK70 */
+      0x04a91854, /* TR4500 */
+      0x04a91855, /* E4200 */
+      0x04a91856, /* TS6200 */
+      0x04a91857, /* TS6280 */
+      0x04a91858, /* TS6230 */
+      0x04a91859, /* TS8200 */
+      0x04a9185a, /* TS8280 */
+      0x04a9185b, /* TS8230 */
+      0x04a9185d, /* TS9580 */
+      0x04a9185e, /* TR9530 */
+      0x04a91863, /* G7000 */
+      0x04a91865, /* G6000 */
+      0x04a91866, /* G6080 */
+      0x04a91869, /* GM4000 */
+      0x04a91873, /* XK80 */
+      0x04a9188b, /* TS5300 */
+      0x04a9188c, /* TS5380 */
+      0x04a9188d, /* TS6300 */
+      0x04a9188e, /* TS6380 */
+      0x04a9188f, /* TS7330 */
+      0x04a91890, /* TS8300 */
+      0x04a91891, /* TS8380 */
+      0x04a91892, /* TS8330 */
+      0x04a91893, /* XK60 */
+      0x04a91894, /* TS6330 */
+      0x04a918a2, /* TS3300 */
+      0x04a918a3, /* E3300 */
+      0x04a9261f, /* MP10 */
+      0x04a9262f, /* MP730 */
+      0x04a92630, /* MP700 */
+      0x04a92635, /* MP5 */
+      0x04a9263c, /* MP360 */
+      0x04a9263d, /* MP370 */
+      0x04a9263e, /* MP390 */
+      0x04a9263f, /* MP375R */
+      0x04a9264c, /* MP740 */
+      0x04a9264d, /* MP710 */
+      0x04a9265d, /* MF5730 */
+      0x04a9265e, /* MF5750 */
+      0x04a9265f, /* MF5770 */
+      0x04a92660, /* MF3110 */
+      0x04a926e6, /* IR1020 */
+      0x04a91706, /* MP750 */
+      0x04a91708, /* MP760 */
+      0x04a91707, /* MP780 */
+      0x04a9170d, /* MP800 */
+      0x04a9170e, /* MP800R */
+      0x04a91713, /* MP830 */
+      0x04a9171a, /* MP810 */
+      0x04a9171b, /* MP960 */
+      0x04a91726, /* MP970 */
+      0x04a91901, /* CS8800F */
+      0x04a9172d, /* MP980 */
+      0x04a91740, /* MP990 */
+      0x04a91908, /* CS9000F */
+      0x04a9174b, /* MG8100 */
+      0x04a91756, /* MG8200 */
+      0x04a9190d, /* CS9000F_MII */
+    };
+
     /* Taken from uld/noarch/oem.conf in the Samsung SANE driver */
     private const uint32 samsung_devices[] = { 0x04e83425, 0x04e8341c, 0x04e8342a, 0x04e8343d, 0x04e83456, 0x04e8345a, 0x04e83427, 0x04e8343a, 0x04e83428, 0x04e8343b, 0x04e83455, 0x04e83421, 0x04e83439, 0x04e83444, 0x04e8343f, 0x04e8344e, 0x04e83431, 0x04e8345c, 0x04e8344d, 0x04e83462, 0x04e83464, 0x04e83461, 0x04e83460, 0x04e8340e, 0x04e83435,
                                                0x04e8340f, 0x04e83441, 0x04e8344f, 0x04e83413, 0x04e8341b, 0x04e8342e, 0x04e83426, 0x04e8342b, 0x04e83433, 0x04e83440, 0x04e83434, 0x04e8345b, 0x04e83457, 0x04e8341f, 0x04e83453, 0x04e8344b, 0x04e83409, 0x04e83412, 0x04e83419, 0x04e8342c, 0x04e8343c, 0x04e83432, 0x04e8342d, 0x04e83430, 0x04e8342f,
@@ -549,6 +761,10 @@ public class SimpleScan : Gtk.Application
                                                0x04e8347c, 0x04e8347e, 0x04e83481, 0x04e83482, 0x04e83331, 0x04e83332, 0x04e83483, 0x04e83484, 0x04e83485, 0x04e83478, 0x04e83325, 0x04e83327, 0x04e8346f, 0x04e83477, 0x04e83324, 0x04e83326, 0x04e83486, 0x04e83487, 0x04e83489
     };
 
+    /* Taken from uld/noarch/oem.conf in the HP/Samsung SANE driver
+       These devices are rebranded Samsung Multifunction Printers. */
+    private const uint32 smfp_devices[] = { 0x03F0AA2A, 0x03F0CE2A, 0x03F0C02A, 0x03F0EB2A, 0x03F0F22A };
+
     /* Taken from /usr/share/hplip/data/models/models.dat in the HPAIO driver */
     private const uint32 hpaio_devices[] = {
       0x04f92311, /* HP Officejet d125xi All-in-One Printer */
@@ -1236,6 +1452,68 @@ public class SimpleScan : Gtk.Application
     /* Taken from epkowa.desc from iscan-data package for Epson driver */
     private const uint32 epkowa_devices[] = { 0x04b80101, 0x04b80102, 0x04b80103, 0x04b80104, 0x04b80105, 0x04b80106, 0x04b80107, 0x04b80108, 0x04b80109, 0x04b8010a, 0x04b8010b, 0x04b8010c, 0x04b8010d, 0x04b8010e, 0x04b8010f, 0x04b80110, 0x04b80112, 0x04b80114, 0x04b80116, 0x04b80118, 0x04b80119, 0x04b8011a, 0x04b8011b, 0x04b8011c, 0x04b8011d, 0x04b8011e, 0x04b8011f, 0x04b80120, 0x04b80121, 0x04b80122, 0x04b80126, 0x04b80128, 0x04b80129, 0x04b8012a, 0x04b8012b, 0x04b8012c, 0x04b8012d, 0x04b8012e, 0x04b8012f, 0x04b80130, 0x04b80131, 0x04b80133, 0x04b80135, 0x04b80136, 0x04b80137, 0x04b80138, 0x04b8013a, 0x04b8013b, 0x04b8013c, 0x04b8013d, 0x04b80142, 0x04b80143, 0x04b80144, 0x04b80147, 0x04b8014a, 0x04b8014b, 0x04b80151, 0x04b80153, 0x04b80801, 0x04b80802, 0x04b80805, 0x04b80806, 0x04b80807, 0x04b80808, 0x04b8080a, 0x04b8080c, 0x04b8080d, 0x04b8080e, 0x04b8080f, 0x04b80810, 0x04b80811, 0x04b80813, 0x04b80814, 0x04b80815, 0x04b80817, 0x04b80818, 0x04b80819, 0x04b8081a, 0x04b8081c, 0x04b8081d, 0x04b8081f, 0x04b80820, 0x04b80821, 0x04b80827, 0x04b80828, 0x04b80829, 0x04b8082a, 0x04b8082b, 0x04b8082e, 0x04b8082f, 0x04b80830, 0x04b80831, 0x04b80833, 0x04b80834, 0x04b80835, 0x04b80836, 0x04b80837, 0x04b80838, 0x04b80839, 0x04b8083a, 0x04b8083c, 0x04b8083f, 0x04b80841, 0x04b80843, 0x04b80844, 0x04b80846, 0x04b80847, 0x04b80848, 0x04b80849, 0x04b8084a, 0x04b8084c, 0x04b8084d, 0x04b8084f, 0x04b80850, 0x04b80851, 0x04b80852, 0x04b80853, 0x04b80854, 0x04b80855, 0x04b80856, 0x04b8085c, 0x04b8085d, 0x04b8085e, 0x04b8085f, 0x04b80860, 0x04b80861, 0x04b80862, 0x04b80863, 0x04b80864, 0x04b80865, 0x04b80866, 0x04b80869, 0x04b8086a, 0x04b80870, 0x04b80871, 0x04b80872, 0x04b80873, 0x04b80878, 0x04b80879, 0x04b8087b, 0x04b8087c, 0x04b8087d, 0x04b8087e, 0x04b8087f, 0x04b80880, 0x04b80881, 0x04b80883, 0x04b80884, 0x04b80885, 0x04b8088f, 0x04b80890, 0x04b80891, 0x04b80892, 0x04b80893, 0x04b80894, 0x04b80895, 0x04b80896, 0x04b80897, 0x04b80898, 0x04b80899, 0x04b8089a, 0x04b8089b, 0x04b8089c, 0x04b8089d, 0x04b8089e, 0x04b8089f, 0x04b808a0, 0x04b808a1, 0x04b808a5, 0x04b808a6, 0x04b808a8, 0x04b808a9, 0x04b808aa, 0x04b808ab, 0x04b808ac, 0x04b808ad, 0x04b808ae, 0x04b808af, 0x04b808b0, 0x04b808b3, 0x04b808b4, 0x04b808b5, 0x04b808b6, 0x04b808b7, 0x04b808b8, 0x04b808b9, 0x04b808bd, 0x04b808be, 0x04b808bf, 0x04b808c0, 0x04b808c1, 0x04b808c3, 0x04b808c4, 0x04b808c5, 0x04b808c6, 0x04b808c7, 0x04b808c8, 0x04b808c9, 0x04b808ca, 0x04b808cd, 0x04b808d0 };
 
+
+    /* Taken from /usr/local/lexmark/unix_scan_drivers/etc/lexmark_nscan.conf */
+    /* Lexmark IDs extracted using command:
+     * grep -r "usb .* /usr" --no-filename --only-matching | sed 's/usb //' | sed 's/ 0x//' | sed 's/ \/usr/,/'
+     */
+    private const uint32 lexmark_nscan_devices[] = {
+    0x043d0279,
+    0x043d027a,
+    0x043d01D6,
+    0x043d01D7,
+    0x043d01D8,
+    0x043d01DC,
+    0x043d01DE,
+    0x043d01E0,
+    0x043d01FA,
+    0x043d01FB,
+    0x043d01FC,
+    0x043d01FD,
+    0x043d01FE,
+    0x043d01FF,
+    0x043d01F4,
+    0x043d0120,
+    0x043d0121,
+    0x043d0128,
+    0x043d014F,
+    0x043d0149,
+    0x043d0152,
+    0x043d0168,
+    0x043d0169,
+    0x043d016A,
+    0x043d012D,
+    0x043d01C4,
+    0x043d01C5,
+    0x043d01C6,
+    0x043d01CF,
+    0x043d01D0,
+    0x043d01D1,
+    0x043d01DB,
+    0x043d01ED,
+    0x043d01F1,
+    0x043d01F5,
+    0x043d0222,
+    0x043d0223,
+    0x043d0227,
+    0x043d0228,
+    0x043d022A,
+    0x043d022B,
+    0x043d022F,
+    0x043d0230,
+    0x043d0231,
+    0x043d0234,
+    0x043d0235,
+    0x043d0244,
+    0x043d0245,
+    0x043d0246,
+    0x043d0247,
+    0x043d0248,
+    0x043d024A,
+    0x043d024E,
+    0x043d024F
+    };
+
     /* Brother IDs extracted using the following Python
      *
      *  import sys
@@ -1273,9 +1551,12 @@ public class SimpleScan : Gtk.Application
         add_devices (driver_map, brscan2_devices, "brscan2");
         add_devices (driver_map, brscan3_devices, "brscan3");
         add_devices (driver_map, brscan4_devices, "brscan4");
+        add_devices (driver_map, pixma_devices, "pixma");
         add_devices (driver_map, samsung_devices, "samsung");
+        add_devices (driver_map, smfp_devices, "smfp");
         add_devices (driver_map, hpaio_devices, "hpaio");
         add_devices (driver_map, epkowa_devices, "epkowa");
+        add_devices (driver_map, lexmark_nscan_devices, "lexmark_nscan");
         var devices = usb_context.get_devices ();
         for (var i = 0; i < devices.length; i++)
         {
@@ -1547,6 +1828,12 @@ public class SimpleScan : Gtk.Application
         scanner.cancel ();
     }
 
+    private void redetect_cb (AppWindow ui)
+    {
+        scanner.redetect ();
+    }
+
+
     private static void log_cb (string? log_domain, LogLevelFlags log_level, string message)
     {
         string prefix;
@@ -1695,7 +1982,7 @@ public class SimpleScan : Gtk.Application
             }
             catch (Error e)
             {
-                stderr.printf ("Error fixing PDF file: %s", e.message);
+                stderr.printf ("Error fixing PDF file: %s\n", e.message);
                 return Posix.EXIT_FAILURE;
             }
             return Posix.EXIT_SUCCESS;
@@ -1715,6 +2002,11 @@ public class SimpleScan : Gtk.Application
         DirUtils.create_with_parents (path, 0700);
         path = Path.build_filename (Environment.get_user_cache_dir (), "simple-scan", "simple-scan.log", null);
         log_file = FileStream.open (path, "w");
+        if (log_file == null )
+        {
+            stderr.printf ("Error: Unable to open %s file for writing\n", path);
+            return Posix.EXIT_FAILURE;
+        }
         Log.set_default_handler (log_cb);
 
         debug ("Starting %s %s, PID=%i", args[0], VERSION, Posix.getpid ());
-- 
cgit v1.2.3