aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor Pashev <pashev.igor@gmail.com>2023-01-06 10:02:49 +0200
committerIgor Pashev <pashev.igor@gmail.com>2023-01-06 10:04:59 +0200
commit1145733c29db0a678537ce99ff60e21613f622a8 (patch)
tree63d5d6c324629d4eef1354db3c97f857d6016a34
downloadiscan-1145733c29db0a678537ce99ff60e21613f622a8.tar.gz
Import iscan 2.30.4-2
-rw-r--r--AUTHORS40
-rw-r--r--COPYING341
-rw-r--r--ChangeLog0
-rw-r--r--Makefile.am154
-rw-r--r--NEWS7
-rw-r--r--README196
-rw-r--r--backend/Makefile.am168
-rw-r--r--backend/backend.c600
-rw-r--r--backend/backend.h380
-rw-r--r--backend/cfg-obj.c1619
-rw-r--r--backend/cfg-obj.h151
-rw-r--r--backend/channel-net.c242
-rw-r--r--backend/channel-pio.c165
-rw-r--r--backend/channel-scsi.c170
-rw-r--r--backend/channel-usb.c285
-rw-r--r--backend/channel.c321
-rw-r--r--backend/channel.h143
-rw-r--r--backend/command.c814
-rw-r--r--backend/command.h223
-rw-r--r--backend/defines.h104
-rw-r--r--backend/device.c563
-rw-r--r--backend/device.h265
-rw-r--r--backend/dip-obj.c746
-rw-r--r--backend/dip-obj.h119
-rw-r--r--backend/epkowa.c6513
-rw-r--r--backend/epkowa.conf65
-rw-r--r--backend/epkowa.h291
-rw-r--r--backend/epkowa_ip.c596
-rw-r--r--backend/epkowa_ip.h124
-rw-r--r--backend/epkowa_ip_api.h92
-rw-r--r--backend/epkowa_scsi.c103
-rw-r--r--backend/epkowa_scsi.h28
-rw-r--r--backend/extension.h177
-rw-r--r--backend/get-infofile.c277
-rw-r--r--backend/get-infofile.h91
-rw-r--r--backend/hw-data.c372
-rw-r--r--backend/hw-data.h136
-rw-r--r--backend/ipc.c563
-rw-r--r--backend/ipc.h121
-rw-r--r--backend/list.c198
-rw-r--r--backend/list.h117
-rw-r--r--backend/message.c152
-rw-r--r--backend/message.h191
-rw-r--r--backend/model-info.c707
-rw-r--r--backend/model-info.h145
-rw-r--r--backend/net-obj.c123
-rw-r--r--backend/net-obj.h89
-rw-r--r--backend/profile.c540
-rw-r--r--backend/tests/45532d48333030.xml28
-rw-r--r--backend/tests/47542d58393730.xml38
-rw-r--r--backend/tests/50657266656374696f6e363130.xml27
-rw-r--r--backend/tests/Makefile.am79
-rw-r--r--backend/tests/network.c86
-rw-r--r--backend/tests/test-cfg-obj.cc60
-rw-r--r--backend/tests/test-cfg-obj.hh254
-rw-r--r--backend/tests/test-model-info.cc83
-rw-r--r--backend/tests/test-model-info.hh241
-rw-r--r--backend/tests/test-net-obj.cc35
-rw-r--r--backend/tests/test-net-obj.hh126
-rwxr-xr-xbackend/tests/xmltest-runner.sh87
-rw-r--r--backend/tests/xmltest.c247
-rw-r--r--backend/tests/xmltest.h190
-rw-r--r--backend/timing.c188
-rw-r--r--backend/timing.h119
-rw-r--r--backend/utils.c334
-rw-r--r--backend/utils.h199
-rw-r--r--backend/xmlreader.c378
-rw-r--r--backend/xmlreader.h79
-rw-r--r--configure.ac378
-rw-r--r--doc/Makefile.am46
-rw-r--r--doc/iscan-registry.man49
-rw-r--r--doc/iscan.man89
-rw-r--r--doc/sane-epkowa.man225
-rw-r--r--doc/xinetd.sane15
-rw-r--r--frontend/Makefile.am102
-rw-r--r--frontend/esmod-wrapper.hh282
-rw-r--r--frontend/file-selector.cc1137
-rw-r--r--frontend/file-selector.h135
-rw-r--r--frontend/gimp-plugin.h186
-rw-r--r--frontend/pisa_aleart_dialog.cc144
-rw-r--r--frontend/pisa_aleart_dialog.h52
-rw-r--r--frontend/pisa_change_unit.cc101
-rw-r--r--frontend/pisa_change_unit.h50
-rw-r--r--frontend/pisa_configuration.cc223
-rw-r--r--frontend/pisa_configuration.h63
-rw-r--r--frontend/pisa_default_val.h89
-rw-r--r--frontend/pisa_enums.h248
-rw-r--r--frontend/pisa_error.cc178
-rw-r--r--frontend/pisa_error.h104
-rw-r--r--frontend/pisa_esmod_structs.h75
-rw-r--r--frontend/pisa_gamma_correction.cc718
-rw-r--r--frontend/pisa_gamma_correction.h85
-rw-r--r--frontend/pisa_gimp.cc665
-rw-r--r--frontend/pisa_gimp.h109
-rw-r--r--frontend/pisa_gimp_1_0_patch.h58
-rw-r--r--frontend/pisa_image_controls.cc381
-rw-r--r--frontend/pisa_image_controls.h50
-rw-r--r--frontend/pisa_img_converter.cc99
-rw-r--r--frontend/pisa_img_converter.h45
-rw-r--r--frontend/pisa_main.cc116
-rw-r--r--frontend/pisa_main.h39
-rw-r--r--frontend/pisa_main_window.cc1746
-rw-r--r--frontend/pisa_main_window.h112
-rw-r--r--frontend/pisa_marquee.cc117
-rw-r--r--frontend/pisa_marquee.h83
-rw-r--r--frontend/pisa_preference.cc351
-rw-r--r--frontend/pisa_preference.h68
-rw-r--r--frontend/pisa_preview_window.cc1844
-rw-r--r--frontend/pisa_preview_window.h171
-rw-r--r--frontend/pisa_progress_window.cc263
-rw-r--r--frontend/pisa_progress_window.h118
-rw-r--r--frontend/pisa_sane_scan.cc1214
-rw-r--r--frontend/pisa_sane_scan.h172
-rw-r--r--frontend/pisa_scan_manager.cc1003
-rw-r--r--frontend/pisa_scan_manager.h151
-rw-r--r--frontend/pisa_scan_selector.cc503
-rw-r--r--frontend/pisa_scan_selector.h72
-rw-r--r--frontend/pisa_scan_tool.cc591
-rw-r--r--frontend/pisa_scan_tool.h63
-rw-r--r--frontend/pisa_settings.cc149
-rw-r--r--frontend/pisa_settings.h75
-rw-r--r--frontend/pisa_structs.h226
-rw-r--r--frontend/pisa_tool.cc184
-rw-r--r--frontend/pisa_tool.h79
-rw-r--r--frontend/pisa_view_manager.cc1134
-rw-r--r--frontend/pisa_view_manager.h227
-rw-r--r--frontend/xpm_data.cc1740
-rw-r--r--frontend/xpm_data.h48
-rw-r--r--gettext.h69
-rw-r--r--include/sane/sanei.h160
-rw-r--r--include/sane/sanei_config.h175
-rw-r--r--include/sane/sanei_debug.h153
-rw-r--r--include/sane/sanei_magic.h154
-rw-r--r--include/sane/sanei_pio.h55
-rw-r--r--include/sane/sanei_scsi.h332
-rw-r--r--include/sane/sanei_usb.h515
-rw-r--r--lib/Makefile.am61
-rw-r--r--lib/basic-imgstream.cc301
-rw-r--r--lib/basic-imgstream.hh144
-rw-r--r--lib/fax-encoder.cc411
-rw-r--r--lib/fax-encoder.hh58
-rw-r--r--lib/file-opener.cc321
-rw-r--r--lib/file-opener.hh101
-rw-r--r--lib/imgstream.cc142
-rw-r--r--lib/imgstream.hh100
-rw-r--r--lib/jpegstream.cc270
-rw-r--r--lib/jpegstream.hh118
-rw-r--r--lib/pcxstream.cc320
-rw-r--r--lib/pcxstream.hh93
-rw-r--r--lib/pdf/Makefile.am44
-rw-r--r--lib/pdf/array.cc115
-rw-r--r--lib/pdf/array.hh87
-rw-r--r--lib/pdf/dictionary.cc123
-rw-r--r--lib/pdf/dictionary.hh93
-rw-r--r--lib/pdf/object.cc103
-rw-r--r--lib/pdf/object.hh120
-rw-r--r--lib/pdf/primitive.cc100
-rw-r--r--lib/pdf/primitive.hh92
-rw-r--r--lib/pdf/writer.cc236
-rw-r--r--lib/pdf/writer.hh165
-rw-r--r--lib/pdfstream.cc351
-rw-r--r--lib/pdfstream.hh105
-rw-r--r--lib/pngstream.cc257
-rw-r--r--lib/pngstream.hh145
-rw-r--r--lib/pnmstream.cc112
-rw-r--r--lib/pnmstream.hh73
-rw-r--r--lib/tests/Makefile.am46
-rw-r--r--lib/tests/even-width.pbmbin0 -> 2427 bytes
-rw-r--r--lib/tests/even-width.pgmbin0 -> 19343 bytes
-rw-r--r--lib/tests/even-width.ppmbin0 -> 57999 bytes
-rw-r--r--lib/tests/odd-width.pbmbin0 -> 2612 bytes
-rw-r--r--lib/tests/odd-width.pgmbin0 -> 19752 bytes
-rw-r--r--lib/tests/odd-width.ppmbin0 -> 59226 bytes
-rw-r--r--lib/tests/pnm.c141
-rw-r--r--lib/tests/pnm.h62
-rwxr-xr-xlib/tests/run-test-pcx.sh76
-rw-r--r--lib/tests/test-pcx.cc85
-rw-r--r--lib/tiffstream.cc268
-rw-r--r--lib/tiffstream.hh105
-rw-r--r--non-free/COPYING.EPSON.en.txt93
-rw-r--r--non-free/COPYING.EPSON.ja.txt93
-rw-r--r--non-free/Makefile.am88
-rw-r--r--non-free/checksums.md54
-rw-r--r--non-free/esmod.hh177
-rwxr-xr-xnon-free/libesmod-i386.c2.sobin0 -> 433532 bytes
-rwxr-xr-xnon-free/libesmod-i386.sobin0 -> 423936 bytes
-rwxr-xr-xnon-free/libesmod-x86_64.c2.sobin0 -> 562256 bytes
-rw-r--r--po/LINGUAS22
-rw-r--r--po/Makefile.in.in366
-rw-r--r--po/Makevars41
-rw-r--r--po/POTFILES.in27
-rw-r--r--po/Rules-quot47
-rw-r--r--po/ar.gmobin0 -> 8892 bytes
-rw-r--r--po/ar.po468
-rw-r--r--po/boldquot.sed10
-rw-r--r--po/de.gmobin0 -> 6909 bytes
-rw-r--r--po/de.po469
-rw-r--r--po/en@boldquot.gmobin0 -> 7679 bytes
-rw-r--r--po/en@boldquot.header25
-rw-r--r--po/en@boldquot.po490
-rw-r--r--po/en@quot.gmobin0 -> 7663 bytes
-rw-r--r--po/en@quot.header22
-rw-r--r--po/en@quot.po487
-rw-r--r--po/es.gmobin0 -> 6826 bytes
-rw-r--r--po/es.po468
-rw-r--r--po/fr.gmobin0 -> 6873 bytes
-rw-r--r--po/fr.po464
-rw-r--r--po/insert-header.sin23
-rw-r--r--po/iscan.pot444
-rw-r--r--po/it.gmobin0 -> 6834 bytes
-rw-r--r--po/it.po465
-rw-r--r--po/ja.gmobin0 -> 7676 bytes
-rw-r--r--po/ja.po460
-rw-r--r--po/ko.gmobin0 -> 5932 bytes
-rw-r--r--po/ko.po460
-rw-r--r--po/nl.gmobin0 -> 6789 bytes
-rw-r--r--po/nl.po467
-rw-r--r--po/pt.gmobin0 -> 6834 bytes
-rw-r--r--po/pt.po466
-rw-r--r--po/quot.sed6
-rw-r--r--po/remove-potcdate.sin19
-rw-r--r--po/stamp-po1
-rw-r--r--po/zh_CN.gmobin0 -> 5334 bytes
-rw-r--r--po/zh_CN.po455
-rw-r--r--po/zh_TW.gmobin0 -> 5354 bytes
-rw-r--r--po/zh_TW.po456
-rw-r--r--sanei/linux_sg3_err.h135
-rw-r--r--sanei/sanei_config.c462
-rw-r--r--sanei/sanei_constrain_value.c304
-rw-r--r--sanei/sanei_init_debug.c141
-rw-r--r--sanei/sanei_magic.c1398
-rw-r--r--sanei/sanei_pio.c611
-rw-r--r--sanei/sanei_scsi.c6180
-rw-r--r--sanei/sanei_usb.c3381
-rw-r--r--scm-build.am110
-rw-r--r--utils/Makefile.am38
-rw-r--r--utils/iscan-registry.in167
237 files changed, 69012 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..1e5d56b
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,40 @@
+"Image Scan! for Linux" was originally written by Noriyoshi Sasaki with
+help from Peter J. Schretlen.
+
+The "burden" of continued development and maintenance was passed on to
+Olaf Meeuwissen during the final stages of preparation for the release
+of version 1.4.0 and Narihiro Matsukuma joined during the preparations
+for 1.6.0 and helped out on and off until 1.14.0. Huynh Tuan Anh lend
+a hand with 2.2.0 and 2.3.0. Nguyen Trang Nhung helped out with 2.8.0.
+Alesh Slovak joined development in the early stages of 2.14.0 and took
+over the torch starting with the 2.20.0 release.
+
+The following people have contributed:
+
+ Ross Boylan added resultion info to PNG images
+ Tobias Kramer pointed out a GTK deficiency wrt locale numerics
+ that wrecked havoc on the GIMP plugin
+ Olaf Meeuwissen rewrote the build system from scratch to use the
+ GNU autotools; upgraded the i18n framework; went
+ thru the trouble of pruning all unnecessary bits
+ from the sources (well, most at least); upgraded
+ the sane-backends bits; merged most changes from
+ the epson backend; overhauled the RPM spec file;
+ completely redid the supported device info; made
+ an interpreter module to make the epkowa backend
+ work in the absence of any libesint*.so (it also
+ lead to more readable code in epkowa.c); removed
+ the most proprietary bits from the sources
+ Johannes Meixner provided some ideas to improve the RPM packaging
+ thru his spec files for the iscan-free and iscan
+ packages from SUSE; sent patches to fix compiler
+ warnings; found a buffer overflow in the backend
+ debugging code and confirmed the fix
+ Dirk O. Siebnich provided the initial patch that inspired support
+ for non-i386 builds
+ Thomas Harding provided the initial changes to fix installation
+ conflicts with Debian's libsane-extras package
+ Khaled Hosny contributed Arabic translations
+ Kåre Särs pointed out bugs in backend options that are not
+ used by iscan but may be used through other SANE
+ frontends
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..11c0c64
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,341 @@
+Please read out the license and Privacy Statement(http://download.ebz.epson.net/ps/linux/).
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ChangeLog
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..2f9d658
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,154 @@
+## Makefile.am -- an automake template for a Makefile.in file
+## Copyright (C) 2004, 2005 Olaf Meeuwissen
+## Copyright (C) 2019 SEIKO EPSON Corporation
+##
+## This file is part of the "Image Scan!" build infra-structure.
+##
+## The "Image Scan!" build infra-structure is free software.
+## You can redistribute it and/or modify it under the terms of the GNU
+## General Public License as published by the Free Software Foundation;
+## either version 2 of the License or at your option any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+## FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+## See the GNU General Public License for more details.
+##
+## You should have received a verbatim copy of the GNU General Public
+## License along with this program; if not, write to:
+##
+## Free Software Foundation, Inc.
+## 59 Temple Place, Suite 330
+## Boston, MA 02111-1307 USA
+
+## Note: SUBDIRS are recursed in the order listed.
+
+SUBDIRS = \
+ lib \
+ backend \
+ non-free \
+ frontend \
+ utils \
+ doc
+
+if ENABLE_FRONTEND
+SUBDIRS += po
+endif
+
+
+ACLOCAL_AMFLAGS = -I m4
+
+M4_MACROS = \
+ m4/codeset.m4 \
+ m4/gettext.m4 \
+ m4/glibc21.m4 \
+ m4/iconv.m4 \
+ m4/intdiv0.m4 \
+ m4/intmax.m4 \
+ m4/inttypes-pri.m4 \
+ m4/inttypes.m4 \
+ m4/inttypes_h.m4 \
+ m4/isc-posix.m4 \
+ m4/lcmessage.m4 \
+ m4/lib-ld.m4 \
+ m4/lib-link.m4 \
+ m4/lib-prefix.m4 \
+ m4/longdouble.m4 \
+ m4/longlong.m4 \
+ m4/nls.m4 \
+ m4/po.m4 \
+ m4/printf-posix.m4 \
+ m4/progtest.m4 \
+ m4/signed.m4 \
+ m4/size_max.m4 \
+ m4/stdint_h.m4 \
+ m4/uintmax_t.m4 \
+ m4/ulonglong.m4 \
+ m4/wchar_t.m4 \
+ m4/wint_t.m4 \
+ m4/xsize.m4
+
+EXTRA_DIST = \
+ $(M4_MACROS) \
+ gettext.h \
+ ltmain.sh.diff \
+ m4/libtool.m4 \
+ NEWS.ja \
+ README.ja \
+ bootstrap \
+ \
+ debian/changelog \
+ debian/control \
+ debian/control.in \
+ debian/copyright \
+ debian/rules \
+ \
+ debian/dll.conf \
+ debian/$(PACKAGE_TARNAME).postinst.in \
+ debian/$(PACKAGE_TARNAME).postrm.in \
+ debian/$(PACKAGE_TARNAME).preinst.in \
+ debian/$(PACKAGE_TARNAME).prerm.in \
+ debian/$(PACKAGE_TARNAME).docs \
+ debian/$(PACKAGE_TARNAME).menu \
+ debian/$(PACKAGE_TARNAME).examples \
+ \
+ $(PACKAGE_TARNAME).desktop \
+ $(PACKAGE_TARNAME).spec
+
+
+update-changelog:
+ > $(srcdir)/ChangeLog
+
+
+## Source tarball related targets
+
+## Things to do before we pack up the sources in a tarball.
+
+dist-hook: \
+ deb-check-control \
+ rpm-check-spec
+
+## Debian packaging support related targets
+
+## Convenience variables to make writing the various Debian related
+## targets a bit easier.
+
+DEB_PACKAGE_NAME = $(PACKAGE_TARNAME)_$(PACKAGE_VERSION)-$(PACKAGE_RELEASE)
+
+## A little QA check to make sure we don't accidentally release with
+## a control file that contains substitutable parameters.
+
+deb-check-control: $(srcdir)/debian/control
+ status=; \
+ for f in $^; do \
+ if `LC_ALL=C $(EGREP) '@\w+@' $$f >/dev/null 2>&1`; then \
+ echo "$$f contains unsubstituted variables; not releasing"; \
+ status=1; \
+ fi; \
+ done; \
+ test x = x$$status || exit 1
+
+.PHONY: deb-check-control
+
+
+## ^L RPM packaging related targets
+
+## A little QA check to make sure we don't accidentally release with
+## a spec file that contains substitutable parameters.
+
+rpm-check-spec: $(srcdir)/$(PACKAGE_TARNAME).spec
+ status=; \
+ for f in $^; do \
+ if `LC_ALL=C $(EGREP) '@\w+@' $$f >/dev/null 2>&1`; then \
+ echo "$$f has unsubstituted variables; not releasing" 1>&2; \
+ status=1; \
+ fi; \
+ done; \
+ test x = x$$status || exit 1
+
+.PHONY: rpm-check-spec
+
+MAINTAINERCLEANFILES =
+MAINTAINERCLEANLOCAL =
+
+include scm-build.am
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..9c40638
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,7 @@
+NEWS -- an overview of user-visible changes with each release
+
+----------------------------------------------------------------------
+iscan-2.30.4 (2019-08-01)
+----------------------------------------------------------------------
+ * update license file
+
diff --git a/README b/README
new file mode 100644
index 0000000..1a03463
--- /dev/null
+++ b/README
@@ -0,0 +1,196 @@
+
+ Image Scan! for Linux
+ =====================
+
+Copyright (C) 2019 SEIKO EPSON Corporation
+
+
+ ABSTRACT
+ --------
+ Image Scan! is a graphical scanner utility for people that do not
+ need all the bells and whistles provided by several of the other
+ utilities out there (xsane, QuiteInsane, Kooka).
+ At the moment it only supports SEIKO EPSON devices. However, the
+ device driver it provides can be used by any other SANE standard
+ compliant scanner utility.
+ Note that several scanners require a non-free plugin before they
+ can be used with this software. Using a scanner directly through
+ a network interface also requires a non-free plugin.
+
+ LICENSING
+ ---------
+ This software is primarily covered by the terms of the GNU General
+ Public License (version 2 or later) with exceptions that allow for
+ the use of several non-free plugins.
+ The graphical user interface, iscan, carries an exception to allow
+ linking with the non-free 'esmod' library. Copies of that library
+ are included in the 'non-free/' directory and covered by the terms
+ of the EPSON END USER SOFTWARE LICENSE.
+ The device driver (backend) carries a similar exception to provide
+ for the use of plugins. Such plugins are packaged and distributed
+ independently from this software and are only required for certain
+ models and/or hardware options.
+
+ A verbatim copy of the GNU General Public License can be found in
+ the file 'COPYING'. A verbatim copy of the EPSON End User Software
+ License can be found in the file 'non-free/COPYING.EPSON.en.txt'.
+
+
+NEWS
+====
+
+ For the latest user visible changes, please refer to the 'NEWS' file.
+
+
+INSTALLATION
+============
+
+ REQUIREMENTS
+ ------------
+ The device information files provided by iscan-data must be installed
+ prior to the installion of Image Scan! for Linux. These files are
+ necessary for Image Scan! for Linux to function properly and may be
+ downloaded via:
+
+ http://download.ebz.epson.net/dsc/search/01/search/?OSC=LX
+
+ BINARY PACKAGES
+ ---------------
+ For help on installation of Debian and RPM binary packages refer to
+ your distribution's documentation. In most graphical environments
+ just double clicking does the right thing. If that does not work
+ for you, you can always install from a command line with the least
+ common denominator tools as follows:
+
+ For Debian packages:
+
+ # dpkg --install iscan_$version-$release_$architecture.deb
+
+ For RPM packages:
+
+ # rpm --upgrade iscan-$version-$release.$architecture.rpm
+
+ Both commands require root user privileges and assume that package
+ dependencies have been taken care of already.
+
+ BUILDING/INSTALLING FROM SOURCE
+ -------------------------------
+ Generic installation notes can be found in the 'INSTALL' file. See
+ the `./configure --help` output for details on additional options.
+
+ When using the `--prefix` option with configure, make sure you do
+ the same when configuring `iscan-data`, otherwise the device
+ information files necessary for the proper functioning of the
+ software will not be found.
+
+ After you have installed the software, you may need to arrange for
+ your device to be recognised automatically by the operating system
+ and give read/write permissions to the appropriate users. Details
+ on how to do this may be found in the SANE documentation:
+
+ http://sane.alioth.debian.org/docs.html
+
+ The binary packages take care of this during their installation as
+ much as possible.
+
+ Users of Debian or RPM based Linux distributions can easily build
+ their own binary packages (if no binaries are available for their
+ distribution already). For Debian based distributions, all you
+ have to do is:
+
+ $ tar xzf iscan_$version-$release.tar.gz
+ $ cd iscan-$version
+ $ dpkg-buildpackage -rfakeroot
+
+ Alternatively, you should be able to use any other Debian package
+ building utility. There are just too many to document ;-)
+
+ For RPM packages, you would just:
+
+ $ rpmbuild -tb iscan_$version-$release.tar.gz
+
+ The above assumes that you have the privileges needed to build RPMs
+ or configured things to build in a user writable location.
+
+
+NON-FREE PLUGINS
+================
+
+ Non-free plugins that add support for certain models and/or hardware
+ options can be downloaded via:
+
+ http://download.ebz.epson.net/dsc/search/01/search/?OSC=LX
+
+ Plugins required and/or available for your device are listed on the
+ download page for the device. Please be sure to download a version
+ that matches your system.
+
+
+SYSTEM REQUIREMENTS
+===================
+
+ We recommend using one of the major Linux distributions with a Linux
+ kernel version of 2.6.3 or later (you can check with `uname -r`) as
+ that is what we normally test with.
+
+ However, the software should still work with kernel versions as far
+ back as 2.4.8. If you happen to use a pre-2.6.3 kernel version you
+ may want to take a look at the info in the 'KNOWN-PROBLEMS' file.
+
+ When scanning large originals and/or at large resolutions, the image
+ files can get pretty big, in the order of tens (or even hundreds) of
+ megabytes for each scan. You need enough free disk space to store
+ such large images. Also note that scanning to printer roughly needs
+ twice the disk space needed for the image in order to succeed.
+
+ The software has been designed so that it does not need to keep the
+ whole image in memory. As a result, 'iscan' can be used on systems
+ with relatively little memory.
+
+
+OPERATION
+=========
+
+ The software can always be started with the 'iscan' command from the
+ command line or your desktop's "command runner" utility.
+ You may also find it in your desktop menu in the "Graphics" section.
+
+ PRINTING
+ --------
+ Scanning "directly" to a printer, by default, sends the image data
+ to your system's default printer using the 'lpr' command. You can
+ modify the command used to print via the "Configuration" button.
+
+ GIMP PLUG-IN
+ ------------
+ The software can also be used as a GIMP plug-in to acquire images
+ directly from the GIMP. After installation of a binary package,
+ the plug-in is (normally) automatically registered and available
+ from the "File" -> "Acquire" or the "File" -> "Create" menu.
+ The exact menu entries may depend on your language settings and
+ GIMP version.
+
+ OTHER SANE FRONTENDS
+ --------------------
+ If you prefer using another SANE frontend, you can use the device
+ driver included with this software by selecting a device with the
+ "epkowa:" substring in its device name.
+
+ NETWORK SCANNING
+ ----------------
+ Scanning directly via a device's network interface is supported
+ with the help of the non-free 'iscan-network-nt' package. Once this
+ package has been installed you need to tell the 'epkowa' backend
+ what network devices it should try to access. The details of how
+ you do this can be found in the 'epkowa.conf' file.
+ This file is included in the sources and typically installed as
+ '/etc/sane.d/epkowa.conf'.
+
+ Notes:
+ 1. 'iscan' does not support setting the IP address of the device
+ itself. Please consult the device manual for instructions on
+ how to do this.
+ 2. The download site only lists the iscan-network-nt module for the
+ devices that are known to work with it.
+ 3. For network scanning via a server connected device please see
+ the user manual.
diff --git a/backend/Makefile.am b/backend/Makefile.am
new file mode 100644
index 0000000..4a36339
--- /dev/null
+++ b/backend/Makefile.am
@@ -0,0 +1,168 @@
+## Makefile.am -- an automake template for a Makefile.in file
+## Copyright (C) 2004, 2005, 2009 Olaf Meeuwissen
+##
+## This file is part of the "Image Scan!" build infra-structure.
+##
+## The "Image Scan!" build infra-structure is free software.
+## You can redistribute it and/or modify it under the terms of the GNU
+## General Public License as published by the Free Software Foundation;
+## either version 2 of the License or at your option any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+## FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+## See the GNU General Public License for more details.
+##
+## You should have received a verbatim copy of the GNU General Public
+## License along with this program; if not, write to:
+##
+## Free Software Foundation, Inc.
+## 59 Temple Place, Suite 330
+## Boston, MA 02111-1307 USA
+
+
+SUBDIRS = . \
+ tests
+
+AM_CPPFLAGS = \
+ -DPIC \
+ -DPKGDATADIR=\"$(datadir)/$(DATA_PKG_NAME)\" \
+ -DMODELDATADIR=\"$(datadir)/$(DATA_PKG_NAME)/$(MODEL_DATA_DIR_NAME)\" \
+ -DPKGLIBDIR=\"$(pkglibdir)\" \
+ -DPKGLOCALSTATEDIR=\"$(localstatedir)/lib/$(PACKAGE_TARNAME)\" \
+ -DSYSCONFDIR=\"$(sysconfdir)\" \
+ -DENABLE_DEBUG=1 \
+ -DMSG_MODULE=\"epkowa\" \
+ -I$(top_srcdir)
+AM_CFLAGS = \
+ -fPIC
+
+exec_sanelibdir = $(libdir)/sane
+exec_sanelib_LTLIBRARIES = \
+ libsane-epkowa.la
+
+libsane_epkowa_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -DBACKEND_NAME=epkowa
+libsane_epkowa_la_LDFLAGS = \
+ -export-symbols-regex ^sane_ \
+ -version-info $(SANE_MAJOR):$(SANE_REVISION):$(SANE_MINOR)
+libsane_epkowa_la_LIBADD = \
+ libepkowa.la
+libsane_epkowa_la_SOURCES = \
+ backend.c \
+ backend.h
+
+noinst_LTLIBRARIES = \
+ libepkowa.la
+
+libepkowa_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(LTDLINCL) \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/non-free \
+ $(XML_CFLAGS) \
+ $(LIBUSB_1_0_CFLAGS) \
+ -DV_MAJOR=$(SANE_MAJOR) -DV_MINOR=$(SANE_MINOR)
+libepkowa_la_LDFLAGS = \
+ -static
+libepkowa_la_LIBADD = \
+ -lm \
+ $(XML_LIBS) \
+ $(LIBUSB_1_0_LIBS) \
+ $(LIBLTDL)
+libepkowa_la_SOURCES = \
+ $(sane_backends_files) \
+ ipc.c \
+ ipc.h \
+ cfg-obj.c \
+ cfg-obj.h \
+ command.c \
+ command.h \
+ defines.h \
+ hw-data.c \
+ hw-data.h \
+ message.c \
+ message.h \
+ net-obj.c \
+ net-obj.h \
+ dip-obj.c \
+ dip-obj.h \
+ device.c \
+ device.h \
+ timing.c \
+ timing.h \
+ utils.c \
+ utils.h \
+ epkowa_ip.c \
+ epkowa_ip.h \
+ epkowa_ip_api.h \
+ channel.c \
+ channel.h \
+ channel-net.c \
+ channel-pio.c \
+ channel-scsi.c \
+ channel-usb.c \
+ model-info.c \
+ model-info.h \
+ list.h \
+ list.c \
+ get-infofile.h \
+ get-infofile.c \
+ xmlreader.h \
+ xmlreader.c
+
+if ENABLE_TIMING
+libepkowa_la_CPPFLAGS += -DENABLE_TIMING=1
+libepkowa_la_LIBADD += -lrt
+endif
+
+EXTRA_DIST = \
+ extension.h \
+ profile.c \
+ epkowa.conf
+
+sane_backends_files = \
+ ../include/sane/sanei.h \
+ ../include/sane/sanei_config.h \
+ ../include/sane/sanei_debug.h \
+ ../include/sane/sanei_magic.h \
+ ../include/sane/sanei_pio.h \
+ ../include/sane/sanei_scsi.h \
+ ../include/sane/sanei_usb.h \
+ ../sanei/linux_sg3_err.h \
+ ../sanei/sanei_config.c \
+ ../sanei/sanei_constrain_value.c \
+ ../sanei/sanei_init_debug.c \
+ ../sanei/sanei_magic.c \
+ ../sanei/sanei_pio.c \
+ ../sanei/sanei_scsi.c \
+ ../sanei/sanei_usb.c \
+ epkowa.c \
+ epkowa.h \
+ epkowa_scsi.c \
+ epkowa_scsi.h
+
+
+## There should be NO libsane.so symlink in $(exec_sanelibdir), but
+## libtool insists on making one for us anyway.
+##
+install-exec-hook:
+ -rm -f $(DESTDIR)$(exec_sanelibdir)/libsane.so.$(SANE_MAJOR)
+
+
+## Minimal sanity checks on the backends we distribute.
+## WARNING: These checks may fail on non-Linux systems for a variety
+## of reasons. If you know why, we would like to know (and
+## patches are naturally welcome).
+check-local:
+ for d in .libs _libs; do \
+ test -d $$d || continue; \
+ for l in $$d/libsane-*.so; do \
+ soname=`objdump -p $$l | awk '/SONAME/ {print $$2}'`; \
+ if test "$$soname" != "libsane.so.$(SANE_MAJOR)"; then \
+ echo "$$l: libsane.so.$(SANE_MAJOR) != $$soname"; \
+ exit 1; \
+ fi; \
+ done; \
+ done
diff --git a/backend/backend.c b/backend/backend.c
new file mode 100644
index 0000000..c9e6cf0
--- /dev/null
+++ b/backend/backend.c
@@ -0,0 +1,600 @@
+/* backend.c -- implements the SANE epkowa backend
+ * Copyright (C) 2008--2009 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+
+#define BACKEND_CREATE_FORWARDERS
+#include "backend.h"
+
+#define BACKEND_BUILD 213
+#define BACKEND_SOURCE PACKAGE_STRING
+
+#include <errno.h>
+#include <ltdl.h>
+#include <string.h>
+
+#include "cfg-obj.h"
+#include "dip-obj.h"
+#include "net-obj.h"
+#include "model-info.h"
+#include "utils.h"
+
+/* Deprecated includes */
+#include "include/sane/sanei_usb.h"
+#include "epkowa.h"
+
+/* Deprecated API calls */
+extern SANE_Status epkowa_open (const char *, SANE_Handle *, const void *);
+
+
+typedef struct
+{
+ void *cfg;
+ void *net;
+ void *dip;
+ list *sane_dev;
+ void **dev_list;
+ void *model_info_cache;
+
+} backend_type;
+
+static backend_type *be = NULL;
+
+
+static void be_sane_dev_dtor (void *);
+
+
+/*! \defgroup SANE_API SANE API Entry Points
+ *
+ * The SANE API entry points make up the \e full API available to the
+ * SANE frontend application programmer. Users of this API should be
+ * careful \e never to assume \e anything about a backend's behaviour
+ * beyond what is required by the SANE standard. The standard can be
+ * retrieved via http://www.sane-project.org/docs.html.
+ *
+ * Whatever documentation may be provided here serves to document the
+ * implementation, if anything. In case of discrepancy with the SANE
+ * specification, the SANE specification is correct.
+ *
+ * @{
+ */
+
+
+/*! \brief Prepares the backend for use by a SANE frontend.
+ *
+ * \remarks
+ * This function \e must be called before any other SANE API entry is
+ * called. It is the only SANE function that may be called after the
+ * sane_exit() function has been called.
+ *
+ * \todo
+ * Deal properly with the \c SANE_Auth_Callback.
+ * It is completely ignored in the current implementation.
+ */
+SANE_Status
+sane_init (SANE_Int *version_code, SANE_Auth_Callback authorize)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ if (be)
+ {
+ log_call ("(%p, %p)", version_code, authorize);
+ err_minor ("backend already initialised");
+ return status;
+ }
+
+ msg_init ();
+ log_call ("(%p, %p)", version_code, authorize);
+ log_info ("%s", BACKEND_SOURCE);
+ log_info ("version %d.%d.%d", SANE_MAJOR, SANE_MINOR, BACKEND_BUILD);
+
+ if (version_code)
+ {
+ *version_code = SANE_VERSION_CODE (SANE_MAJOR, SANE_MINOR,
+ BACKEND_BUILD);
+ }
+
+ if (authorize)
+ {
+ err_minor ("authorisation not supported");
+ }
+
+ be = t_calloc (1, backend_type);
+ if (!be)
+ {
+ return SANE_STATUS_NO_MEM;
+ }
+
+ /* Needs to be done _before_ cfg_init() because that refers to the
+ * model_info_cache somewhere deep down in its bowels.
+ */
+ be->model_info_cache = model_info_cache_init (MODELDATADIR, &status);
+ if (!be->model_info_cache)
+ {
+ sane_exit ();
+ return status;
+ }
+
+ be->cfg = cfg_init (PKGDATADIR, &status);
+ if (!be->cfg)
+ {
+ sane_exit ();
+ return status;
+ }
+
+ if (cfg_has (be->cfg, CFG_KEY_NET))
+ {
+ be->net = net_init (PKGLIBDIR, &status);
+ if (!be->net)
+ {
+ if (SANE_STATUS_GOOD != status)
+ err_fatal ("%s", sane_strstatus (status));
+ err_major ("disabling network device support");
+ cfg_set (be->cfg, CFG_KEY_NET, false);
+ status = SANE_STATUS_GOOD;
+ }
+ }
+ if (cfg_has (be->cfg, CFG_KEY_PIO))
+ {
+ /* place holder */
+ }
+ if (cfg_has (be->cfg, CFG_KEY_SCSI))
+ {
+ /* place holder */
+ }
+ if (cfg_has (be->cfg, CFG_KEY_USB))
+ {
+ sanei_usb_init ();
+ }
+
+ if (cfg_has (be->cfg, CFG_KEY_INTERPRETER))
+ {
+ if (0 != lt_dlinit ())
+ {
+ err_fatal ("%s", lt_dlerror ());
+ err_major ("disabling interpreter support");
+ cfg_set (be->cfg, CFG_KEY_INTERPRETER, false);
+ }
+ }
+
+ be->dip = dip_init (PKGLIBDIR, &status);
+ if (!be->dip)
+ {
+ sane_exit ();
+ return status;
+ }
+
+ return status;
+}
+
+
+/*! \brief Releases all resources held by the backend.
+ *
+ * \remarks
+ * Applications \e must call this function to terminate use of the
+ * backend. After it has been called, sane_init() has to be called
+ * before other SANE API can be used. The function needs to close
+ * any open handles.
+ *
+ * \remarks
+ * The implementation must be able to deal properly with a partially
+ * initialised backend so that sane_init() can use this function for
+ * its error recovery.
+ */
+void
+sane_exit (void)
+{
+ log_call ("()");
+
+ if (!be)
+ {
+ msg_init ();
+ err_minor ("backend is not initialized");
+ return;
+ }
+
+ be->dip = dip_exit (be->dip);
+
+ if (cfg_has (be->cfg, CFG_KEY_INTERPRETER))
+ {
+ lt_dlexit ();
+ }
+
+ if (cfg_has (be->cfg, CFG_KEY_USB))
+ {
+ /* sanei_usb_exit, if only there was one! */
+ }
+ if (cfg_has (be->cfg, CFG_KEY_SCSI))
+ {
+ /* place holder */
+ }
+ if (cfg_has (be->cfg, CFG_KEY_PIO))
+ {
+ /* place holder */
+ }
+ if (be->net)
+ {
+ be->net = net_exit (be->net);
+ }
+
+ be->cfg = cfg_exit (be->cfg);
+ delete (be->dev_list);
+ list_destroy (be->sane_dev, be_sane_dev_dtor);
+
+ be->model_info_cache = model_info_cache_exit (be->model_info_cache);
+
+ delete (be);
+}
+
+
+/*! \brief Creates a list of devices available through the backend.
+ *
+ * \remarks
+ * The returned \a device_list \e must remain unchanged and valid
+ * until this function is called again or sane_exit() is called.
+ *
+ * \remarks
+ * Applications are \e not required to call this function before
+ * they call sane_open().
+ */
+SANE_Status
+sane_get_devices (const SANE_Device ***device_list, SANE_Bool local_only)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+ list *sane_dev = NULL;
+
+ log_call ("(%p, %d)", device_list, local_only);
+
+ if (!be)
+ {
+ msg_init ();
+ err_fatal ("backend is not initialized");
+ return SANE_STATUS_ACCESS_DENIED;
+ }
+
+ if (!device_list)
+ {
+ err_fatal ("%s", strerror (EINVAL));
+ return SANE_STATUS_INVAL;
+ }
+
+ sane_dev = list_create ();
+ if (sane_dev)
+ {
+ if (!local_only && cfg_has (be->cfg, CFG_KEY_NET))
+ {
+ cfg_find (be->cfg, CFG_KEY_NET, sane_dev);
+ }
+ if (cfg_has (be->cfg, CFG_KEY_PIO))
+ {
+ cfg_find (be->cfg, CFG_KEY_PIO, sane_dev);
+ }
+ if (cfg_has (be->cfg, CFG_KEY_SCSI))
+ {
+ cfg_find (be->cfg, CFG_KEY_SCSI, sane_dev);
+ }
+ if (cfg_has (be->cfg, CFG_KEY_USB))
+ {
+ cfg_find (be->cfg, CFG_KEY_USB, sane_dev);
+ }
+
+ if (cfg_has (be->cfg, CFG_KEY_INTERPRETER))
+ {
+ cfg_find (be->cfg, CFG_KEY_INTERPRETER, sane_dev);
+ }
+
+ if (be->sane_dev)
+ {
+ delete (be->dev_list);
+ list_destroy (be->sane_dev, be_sane_dev_dtor);
+ }
+ be->sane_dev = sane_dev;
+ }
+
+ be->dev_list = list_normalize (be->sane_dev);
+ *device_list = (const SANE_Device **) be->dev_list;
+ if (!*device_list)
+ {
+ status = SANE_STATUS_NO_MEM;
+ }
+
+ return status;
+}
+
+
+/*! \brief Establishes a connection to a named device.
+ *
+ * \remarks
+ * Applications are allowed to call this function directly, without a
+ * call to sane_get_devices() first. An empty string may be used for
+ * the \a device_name to request the first available device.
+ *
+ * \todo Register the handle with \c be before \c SANE_STATUS_GOOD is
+ * returned so we can check in the other API entries whether we
+ * got a known handle passed in as well as call sane_close() on
+ * all open handles in sane_exit().
+ */
+SANE_Status
+sane_open (SANE_String_Const device_name, SANE_Handle *handle)
+{
+ const SANE_Device *sane_dev = NULL;
+
+ log_call ("(%s, %p)", device_name, handle);
+
+ if (!be)
+ {
+ msg_init ();
+ err_fatal ("backend is not initialized");
+ return SANE_STATUS_ACCESS_DENIED;
+ }
+
+ if (!handle)
+ {
+ err_fatal ("%s", strerror (EINVAL));
+ return SANE_STATUS_INVAL;
+ }
+
+ if (!device_name)
+ {
+ /* The SANE API specification explicitly talks about a zero
+ * length string. There is no mention about what should be
+ * done in the case where there is NO string at all.
+ * We degrade gracefully.
+ */
+ err_minor ("assuming frontend meant to pass an empty string");
+ }
+
+ if (!be->sane_dev)
+ { /* FIXME: does more than necessary */
+ const SANE_Device **dev = NULL;
+ sane_get_devices (&dev, false);
+ }
+
+ if (0 == list_size (be->sane_dev))
+ {
+ err_major ("no supported devices available");
+ return SANE_STATUS_ACCESS_DENIED;
+ }
+
+ if (!device_name || 0 == strlen (device_name))
+ {
+ sane_dev = be->sane_dev->head->data;
+ }
+ else
+ {
+ list_reset (be->sane_dev);
+ while ((sane_dev = list_next (be->sane_dev))
+ && 0 != strcmp_c (sane_dev->name, device_name))
+ {
+ /* nothing to do, condition does all the processing */
+ }
+ }
+
+ if (!sane_dev)
+ {
+ err_major ("no such device");
+ return SANE_STATUS_INVAL;
+ }
+
+ return epkowa_open (sane_dev->name, handle, be->dip);
+}
+
+//! Obtains the current scan parameters for a device
+/*! \remarks
+ * The parameters are only guaranteed to be accurate between a call
+ * to sane_start() and the completion of that request. Outside of
+ * that scope the parameters are a best effort only and the backend
+ * is at liberty to change them.
+ */
+SANE_Status
+sane_get_parameters (SANE_Handle handle, SANE_Parameters *parameters)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+ Epson_Scanner *s;
+
+ log_call ("(%p, %p)", handle, parameters);
+
+ if (!handle || !parameters)
+ {
+ err_fatal ("%s", strerror (EINVAL));
+ return SANE_STATUS_INVAL;
+ }
+
+ s = (Epson_Scanner *) handle;
+
+ if (s->src->transfer_started && !s->src->transfer_stopped)
+ {
+ static const char *const color_space[] = {
+ "GRAY", "RGB", "RED", "GREEN", "BLUE"
+ };
+
+ const SANE_Parameters *p = &s->src->ctx;
+
+ log_info
+ ("Scan area : %.2f x %.2f [mm^2]",
+ SANE_UNFIX (s->val[OPT_BR_X].w) - SANE_UNFIX (s->val[OPT_TL_X].w),
+ SANE_UNFIX (s->val[OPT_BR_Y].w) - SANE_UNFIX (s->val[OPT_TL_Y].w));
+ log_info
+ ("Offset : (%.2f, %.2f) [mm]",
+ SANE_UNFIX (s->val[OPT_TL_X].w), SANE_UNFIX (s->val[OPT_TL_Y].w));
+
+ log_info ("Color space : %s-%d", color_space[p->format], p->depth);
+ log_info ("Image size : %d x %d [pixels^2] (%.2f x %.2f [mm^2])",
+ p->pixels_per_line, p->lines,
+ p->pixels_per_line * MM_PER_INCH / s->val[OPT_X_RESOLUTION].w,
+ p->lines * MM_PER_INCH / s->val[OPT_Y_RESOLUTION].w);
+ log_info ("X Resolution: %d [dpi]", s->val[OPT_X_RESOLUTION].w);
+ log_info ("Y Resolution: %d [dpi]", s->val[OPT_Y_RESOLUTION].w);
+
+ memcpy (parameters, p, sizeof (*p));
+ }
+ else
+ {
+ status = estimate_parameters (s, parameters);
+ }
+
+ return status;
+}
+
+/*! \brief Acquires up to \a max_length bytes of new image data.
+ *
+ * \remarks
+ * The \a length is guaranteed to be zero in case of an unsuccessful
+ * request.
+ *
+ * \remarks
+ * The implementation allows for \c NULL as a \a buffer value. This
+ * caters to a frontends that do not prepare buffer space when they
+ * expect a \c SANE_STATUS_EOF return value.
+ */
+SANE_Status
+sane_read (SANE_Handle handle, SANE_Byte *buffer,
+ SANE_Int max_length, SANE_Int *length)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+ Epson_Scanner *s;
+
+ log_call ("(%p, %p, %i, %p)", handle, buffer, max_length, length);
+
+ if (length) *length = 0;
+
+ if (!handle)
+ {
+ err_fatal ("%s", strerror (EINVAL));
+ return SANE_STATUS_INVAL;
+ }
+
+ s = (Epson_Scanner *) handle;
+
+ require (s->src == &s->raw || s->src == &s->img);
+
+ if (s->src == &s->raw)
+ {
+ status = fetch_image_data (s, buffer, max_length, length);
+ }
+ else if (s->src == &s->img)
+ {
+ /**/ if (!s->img.ptr)
+ {
+ err_major ("%s", strerror (ENOMEM));
+ status = SANE_STATUS_NO_MEM;
+ }
+ else if (s->img.ptr == s->img.end)
+ {
+ status = SANE_STATUS_EOF;
+ }
+ else if (s->img.cancel_requested)
+ {
+ s->img.transfer_stopped = true;
+ status = SANE_STATUS_CANCELLED;
+ }
+ else if (buffer && 0 < max_length)
+ {
+ SANE_Int len = s->img.end - s->img.ptr;
+
+ if (len > max_length) len = max_length;
+ memcpy (buffer, s->img.ptr, len);
+ s->img.ptr += len;
+ if (length) *length = len;
+ }
+ else
+ {
+ status = SANE_STATUS_NO_MEM;
+ }
+ }
+
+ if (SANE_STATUS_EOF == status)
+ {
+ s->src->transfer_stopped = true;
+ }
+
+ return status;
+}
+
+/*!
+ @}
+ */
+
+
+/*! Releases the resources held by a SANE_Device.
+
+ This function is primarily useful to maintain the \c sane_dev
+ member of a backend_type object.
+ */
+static void
+be_sane_dev_dtor (void *p)
+{
+ SANE_Device *sd = (SANE_Device *) p;
+ if (!sd) return;
+
+ const_delete (sd->name , SANE_String);
+ const_delete (sd->vendor, SANE_String);
+ const_delete (sd->model , SANE_String);
+ const_delete (sd->type , SANE_String);
+
+ delete (sd);
+}
diff --git a/backend/backend.h b/backend/backend.h
new file mode 100644
index 0000000..848f5a7
--- /dev/null
+++ b/backend/backend.h
@@ -0,0 +1,380 @@
+/* backend.h -- SANE backend implementation boiler plate
+ * Copyright (C) 2007 EPSON AVASYS CORPORATION
+ * Copyright (C) 2008 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifndef included_backend_h
+#define included_backend_h
+
+/*! \file
+ * \brief Provides backend implementation boiler plate.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sane/sane.h>
+#include <stdio.h>
+
+
+#ifndef BACKEND_NAME
+#error "Define the BACKEND_NAME macro to match your backend's name"
+#error "before including this file (backend.h)."
+#error "The value should not be quoted. Characters in your backend"
+#error "name that are not allowed in C variables should be replaced"
+#error "by underscores, one for each such character."
+#endif /* !defined (BACKEND_NAME) */
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+/* Set up the machinery used to create the "shadow" API entries.
+ * FIXME: Move these #define's into their own header file so that
+ * multi-file plug-ins can use them too.
+ * FIXME: Add an api_entry() function that mimicks the API_ENTRY()
+ * #define so that plug-ins can use dlsym() and friends more
+ * easily. You need a way to create names at run-time. The
+ * #define only works at compile-time!
+ */
+#define CONCAT_INTERNAL(s1,s2) s1##s2
+#define CONCAT(s1,s2) CONCAT_INTERNAL(s1,s2)
+
+#define API_ENTRY(backend,function) \
+ CONCAT(sane_,CONCAT(backend,CONCAT(_,function)))
+
+/* For each API entry, set up "shadow" API entries with a unique name.
+ */
+
+SANE_Status
+API_ENTRY (BACKEND_NAME, init) (SANE_Int *version,
+ SANE_Auth_Callback authoriser);
+void
+API_ENTRY (BACKEND_NAME, exit) (void);
+
+SANE_Status
+API_ENTRY (BACKEND_NAME, get_devices) (const SANE_Device ***device_list,
+ SANE_Bool local_only);
+
+SANE_Status
+API_ENTRY (BACKEND_NAME, open) (SANE_String_Const device_name,
+ SANE_Handle *handle);
+void
+API_ENTRY (BACKEND_NAME, close) (SANE_Handle handle);
+
+const SANE_Option_Descriptor *
+API_ENTRY (BACKEND_NAME, get_option_descriptor) (SANE_Handle handle,
+ SANE_Int index);
+SANE_Status
+API_ENTRY (BACKEND_NAME, control_option) (SANE_Handle handle, SANE_Int index,
+ SANE_Action action, void *value,
+ SANE_Word *info);
+SANE_Status
+API_ENTRY (BACKEND_NAME, get_parameters) (SANE_Handle handle,
+ SANE_Parameters *parameters);
+
+SANE_Status
+API_ENTRY (BACKEND_NAME, start) (SANE_Handle handle);
+SANE_Status
+API_ENTRY (BACKEND_NAME, read) (SANE_Handle handle, SANE_Byte *buffer,
+ SANE_Int max_length, SANE_Int *length);
+void
+API_ENTRY (BACKEND_NAME, cancel) (SANE_Handle handle);
+
+SANE_Status
+API_ENTRY (BACKEND_NAME, set_io_mode) (SANE_Handle handle,
+ SANE_Bool non_blocking);
+SANE_Status
+API_ENTRY (BACKEND_NAME, get_select_fd) (SANE_Handle handle,
+ SANE_Int *fdp);
+
+
+SANE_String_Const
+API_ENTRY (BACKEND_NAME, strstatus) (SANE_Status status);
+
+
+#ifdef BACKEND_CREATE_FORWARDERS
+
+/* For each API entry, forward the original call to the "shadow" API
+ * entry just declared.
+ * Most of the argument validation can be added here.
+ */
+
+SANE_Status
+sane_init (SANE_Int *version, SANE_Auth_Callback authoriser)
+{
+ SANE_Status status;
+
+ status = API_ENTRY (BACKEND_NAME, init) (version, authoriser);
+ return status;
+}
+
+void
+sane_exit (void)
+{
+ API_ENTRY (BACKEND_NAME, exit) ();
+ return;
+}
+
+
+SANE_Status
+sane_get_devices (const SANE_Device ***device_list, SANE_Bool local_only)
+{
+ SANE_Status status;
+
+ status = API_ENTRY (BACKEND_NAME, get_devices) (device_list, local_only);
+ return status;
+}
+
+
+SANE_Status
+sane_open (SANE_String_Const device_name, SANE_Handle *handle)
+{
+ SANE_Status status;
+
+ status = API_ENTRY (BACKEND_NAME, open) (device_name, handle);
+ return status;
+}
+
+void
+sane_close (SANE_Handle handle)
+{
+ API_ENTRY (BACKEND_NAME, close) (handle);
+ return;
+}
+
+
+const SANE_Option_Descriptor *
+sane_get_option_descriptor (SANE_Handle handle, SANE_Int index)
+{
+ const SANE_Option_Descriptor *desc;
+
+ desc = API_ENTRY (BACKEND_NAME, get_option_descriptor) (handle, index);
+ return desc;
+}
+
+SANE_Status
+sane_control_option (SANE_Handle handle, SANE_Int index, SANE_Action action,
+ void *value, SANE_Word *info)
+{
+ SANE_Status status;
+
+ status = API_ENTRY (BACKEND_NAME, control_option) (handle, index, action,
+ value, info);
+ return status;
+}
+
+SANE_Status
+sane_get_parameters (SANE_Handle handle, SANE_Parameters *parameters)
+{
+ SANE_Status status;
+
+ status = API_ENTRY (BACKEND_NAME, get_parameters) (handle, parameters);
+ return status;
+}
+
+
+SANE_Status
+sane_start (SANE_Handle handle)
+{
+ SANE_Status status;
+
+ status = API_ENTRY (BACKEND_NAME, start) (handle);
+ return status;
+}
+
+SANE_Status
+sane_read (SANE_Handle handle, SANE_Byte *buffer, SANE_Int max_length,
+ SANE_Int *length)
+{
+ SANE_Status status;
+
+ status = API_ENTRY (BACKEND_NAME, read) (handle, buffer, max_length,
+ length);
+ return status;
+}
+
+void
+sane_cancel (SANE_Handle handle)
+{
+ API_ENTRY (BACKEND_NAME, cancel) (handle);
+ return;
+}
+
+
+SANE_Status
+sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
+{
+ SANE_Status status;
+
+ status = API_ENTRY (BACKEND_NAME, set_io_mode) (handle, non_blocking);
+ return status;
+}
+
+SANE_Status
+sane_get_select_fd (SANE_Handle handle, SANE_Int *fdp)
+{
+ SANE_Status status;
+
+ status = API_ENTRY (BACKEND_NAME, get_select_fd) (handle, fdp);
+ return status;
+}
+
+SANE_String_Const
+sane_strstatus (SANE_Status status)
+{
+ SANE_String_Const str;
+
+ str = API_ENTRY (BACKEND_NAME, strstatus) (status);
+ return str;
+}
+
+/*! \brief Marks a string for the translation tools.
+ */
+#define N(string) (string)
+
+/* Backends should not be bothered with implementing their own
+ * version of this bit of SANE API. At best, all will do the same
+ * thing, at worst every one has its own implementation returning
+ * different strings for the same status. This implementation will
+ * be used by every backend without them even noticing.
+ */
+SANE_String_Const
+API_ENTRY (BACKEND_NAME, strstatus) (SANE_Status status)
+{
+ switch (status)
+ {
+ case SANE_STATUS_GOOD:
+ return N("Success");
+ case SANE_STATUS_UNSUPPORTED:
+ return N("Operation not supported");
+ case SANE_STATUS_CANCELLED:
+ return N("Operation was cancelled");
+ case SANE_STATUS_DEVICE_BUSY:
+ return N("Device busy");
+ case SANE_STATUS_INVAL:
+ return N("Invalid argument");
+ case SANE_STATUS_EOF:
+ return N("End of file reached");
+ case SANE_STATUS_JAMMED:
+ return N("Document feeder jammed");
+ case SANE_STATUS_NO_DOCS:
+ return N("Document feeder out of documents");
+ case SANE_STATUS_COVER_OPEN:
+ return N("Scanner cover is open");
+ case SANE_STATUS_IO_ERROR:
+ return N("Error during device I/O");
+ case SANE_STATUS_NO_MEM:
+ return N("Out of memory");
+ case SANE_STATUS_ACCESS_DENIED:
+ return N("Access to resource has been denied");
+
+ default:
+ {
+ static char msg[80]; /* not re-entrant! */
+
+ snprintf (msg, 80, N("Unknown status code (%d)"), status);
+ return msg;
+ }
+ }
+}
+
+#undef N /* no longer needed, clean up */
+
+
+#endif /* !defined (BACKEND_CREATE_FORWARDERS) */
+
+
+/* Use the preprocessor to rename API entries in the backend to match
+ * the "shadow" API entries we have set up above.
+ * This way, the implementer can ignore all these name playing games.
+ */
+
+#define sane_init API_ENTRY (BACKEND_NAME, init)
+#define sane_exit API_ENTRY (BACKEND_NAME, exit)
+#define sane_get_devices \
+ API_ENTRY (BACKEND_NAME, get_devices)
+#define sane_open API_ENTRY (BACKEND_NAME, open)
+#define sane_close API_ENTRY (BACKEND_NAME, close)
+#define sane_get_option_descriptor \
+ API_ENTRY (BACKEND_NAME, get_option_descriptor)
+#define sane_control_option \
+ API_ENTRY (BACKEND_NAME, control_option)
+#define sane_get_parameters \
+ API_ENTRY (BACKEND_NAME, get_parameters)
+#define sane_start API_ENTRY (BACKEND_NAME, start)
+#define sane_read API_ENTRY (BACKEND_NAME, read)
+#define sane_cancel API_ENTRY (BACKEND_NAME, cancel)
+#define sane_set_io_mode \
+ API_ENTRY (BACKEND_NAME, set_io_mode)
+#define sane_get_select_fd \
+ API_ENTRY (BACKEND_NAME, get_select_fd)
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* !defined (included_backend_h) */
diff --git a/backend/cfg-obj.c b/backend/cfg-obj.c
new file mode 100644
index 0000000..e26901c
--- /dev/null
+++ b/backend/cfg-obj.c
@@ -0,0 +1,1619 @@
+/* cfg-obj.c -- configuration objects
+ * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+/*! \file
+ * \todo Split the probing responsibility off into the respective
+ * channel classes.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "cfg-obj.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <locale.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "command.h"
+#include "hw-data.h"
+#include "net-obj.h"
+#include "model-info.h"
+#include "ipc.h"
+#include "utils.h"
+
+/* Deprecated includes */
+#include "include/sane/sanei_usb.h"
+#include "include/sane/sanei_scsi.h"
+
+
+#ifndef SANE_CONFIG_DIR_NAME
+#define SANE_CONFIG_DIR_NAME "/etc/sane.d"
+#endif
+
+#define DEV_NAME_SEP_STR ":"
+#define DEV_NAME_SEP DEV_NAME_SEP_STR[0]
+
+
+const char *cfg_file_name = "epkowa.conf";
+
+
+typedef struct
+{
+ bool active[_CFG_KEY_ID_TERMINATOR_];
+ list * seen[_CFG_KEY_ID_TERMINATOR_];
+
+} cfg_type;
+
+static cfg_type *_cfg = NULL;
+
+static cfg_key_type _cfg_key[] =
+ {
+ "net",
+ "pio",
+ "scsi",
+ "usb",
+
+ "interpreter",
+ "fs-blacklist",
+
+ "option",
+ };
+
+static char* _opt_vals[] =
+ {
+ "prefer-adf",
+ };
+
+
+static FILE *_cfg_fopen_conf (const char *filename);
+static FILE *_cfg_fopen_data (const char *dirname, const char *key);
+
+
+static cfg_key_id_type _cfg_getline (char **line, size_t *size, FILE *fp);
+
+
+static bool _cfg_is_bare_key (const char *string);
+static bool _cfg_is_valid_net_entry (const char *string);
+static bool _cfg_is_valid_scsi_entry (const char *string);
+static bool _cfg_is_valid_usb_entry (const char *string);
+static bool _cfg_is_valid_interpreter_entry (const char *string);
+static bool _cfg_is_valid_fs_blacklist_entry (const char *string);
+static bool _cfg_is_valid_option_entry (const char *string);
+
+typedef bool (*validator) (const char *);
+
+static validator _cfg_validate[] =
+ {
+ _cfg_is_valid_net_entry,
+ _cfg_is_bare_key,
+ _cfg_is_valid_scsi_entry,
+ _cfg_is_valid_usb_entry,
+
+ _cfg_is_valid_interpreter_entry,
+ _cfg_is_valid_fs_blacklist_entry,
+
+ _cfg_is_valid_option_entry,
+ };
+
+
+static bool _cfg_register_no_op (const char *string);
+static bool _cfg_register_net_entry (const char *string);
+static bool _cfg_register_scsi_entry (const char *string);
+static bool _cfg_register_usb_entry (const char *string);
+static bool _cfg_register_interpreter_entry (const char *string);
+static bool _cfg_register_fs_blacklist_entry (const char *string);
+static bool _cfg_register_option_entry (const char *string);
+
+typedef bool (*registrar) (const char *);
+
+static registrar _cfg_register[] =
+ {
+ _cfg_register_net_entry,
+ _cfg_register_no_op,
+ _cfg_register_scsi_entry,
+ _cfg_register_usb_entry,
+
+ _cfg_register_interpreter_entry,
+ _cfg_register_fs_blacklist_entry,
+
+ _cfg_register_option_entry,
+ };
+
+
+static void _cfg_probe_no_op (list *dev_list);
+static void _cfg_probe_net (list *dev_list);
+static void _cfg_probe_scsi (list *dev_list);
+static void _cfg_probe_usb (list *dev_list);
+static void _cfg_probe_interpreter (list *dev_list);
+
+typedef void (*probe) (list *);
+
+static probe _cfg_probe[] =
+ {
+ _cfg_probe_net,
+ _cfg_probe_no_op,
+ _cfg_probe_scsi,
+ _cfg_probe_usb,
+
+ _cfg_probe_interpreter,
+ _cfg_probe_no_op,
+
+ _cfg_probe_no_op,
+ };
+
+
+static SANE_Status _cfg_attach (SANE_String_Const dev_name, list *dev_list);
+static SANE_String_Const _cfg_get_vendor (SANE_String_Const dev_name);
+static SANE_String_Const _cfg_get_model (SANE_String_Const dev_name);
+static SANE_String_Const _cfg_get_type (SANE_String_Const dev_name);
+
+static bool _cfg_have_interpreter (const char *library, const char *firmware);
+
+/* Helper stuff to hack around sanei_usb/sanei_scsi API requirements
+ used in the various _cfg_probe_*() implementations.
+ */
+static list *_cfg_dev_list = NULL;
+static const char *_cfg_dev_key = NULL;
+static SANE_Status _cfg_scsi_attach (SANE_String_Const dev_name);
+static SANE_Status _cfg_usb_attach (SANE_String_Const dev_name);
+
+
+static void _cfg_net_dtor (void *);
+static void _cfg_scsi_dtor (void *);
+static void _cfg_interpreter_dtor (void *);
+
+typedef void (*destructor) (void *);
+
+static destructor _cfg_dtor[] =
+ {
+ _cfg_net_dtor,
+ free,
+ _cfg_scsi_dtor,
+ free,
+
+ _cfg_interpreter_dtor,
+ free,
+
+ free,
+ };
+
+
+/*! Creates and initialises a configuration object.
+
+ The configuration object is initialised with per key data files in
+ the \a pkgdatadir and a configuration file, \c cfg_file_name. The
+ configuration file is searched for in list of directories with the
+ first file found being used.
+
+ The list of directories can be customised via a \c SANE_CONFIG_DIR
+ environment variable. This variable uses the exact same syntax as
+ the \c PATH environment variable with this difference that a final
+ colon, \c :, appends the default locations.
+
+ When no \c SANE_CONFIG_DIR variable is set, the default locations
+ will be used. The default locations are, in order:
+ - the process' current working directory
+ - the system's SANE configuration directory
+
+ On most system's the above is equivalent to setting:
+ \code
+ SANE_CONFIG_DIR=.:/etc/sane.d
+ \endcode
+ */
+void *
+cfg_init (const char *pkgdatadir, SANE_Status *status)
+{
+ SANE_Status s = SANE_STATUS_GOOD;
+
+ char *lc_ctype = NULL;
+ FILE *fp = NULL;
+ cfg_key_id_type id;
+
+ log_call ("(%s, %p)", pkgdatadir, status);
+ require (num_of (_cfg_key) == _CFG_KEY_ID_TERMINATOR_);
+
+ if (_cfg)
+ {
+ if (pkgdatadir) err_minor ("been here, done that");
+ if (status) *status = s;
+ return _cfg;
+ }
+
+ _cfg = t_calloc (1, cfg_type);
+ if (!_cfg)
+ {
+ if (status) *status = SANE_STATUS_NO_MEM;
+ return _cfg;
+ }
+
+ lc_ctype = setlocale (LC_CTYPE, "C");
+
+ if (pkgdatadir)
+ {
+ /* Look for data files containing information about the
+ * devices we "officially" support. Keywords without a
+ * corresponding data file are not an error.
+ */
+ id = 0;
+ while (num_of (_cfg_key) > id)
+ {
+ fp = _cfg_fopen_data (pkgdatadir, _cfg_key[id]);
+ if (fp)
+ {
+ size_t size = 0;
+ char *line = NULL;
+
+ while (_cfg_getline (&line, &size, fp) == id)
+ {
+ log_info ("line: '%s'", line);
+
+ if (0 != strcmp_c (line, _cfg_key[id]))
+ {
+ _cfg_register[id] (line);
+ }
+ }
+ delete (line);
+ if (fclose (fp))
+ {
+ err_minor ("%s%s%s: %s",
+ pkgdatadir, FILE_SEP_STR, _cfg_key[id],
+ strerror (errno));
+ }
+ }
+ ++id;
+ }
+
+ /* Allow for interpreter based packages to (un)register
+ * themselves upon (un)installation in a suitable place
+ * so we don't have to track them anymore.
+ */
+ id = CFG_KEY_INTERPRETER;
+ fp = _cfg_fopen_data (PKGLOCALSTATEDIR, _cfg_key[id]);
+ if (fp)
+ {
+ size_t size = 0;
+ char *line = NULL;
+
+ while (_cfg_getline (&line, &size, fp) == id)
+ {
+ log_info ("line: '%s'", line);
+
+ if (0 != strcmp_c (line, _cfg_key[id]))
+ {
+ _cfg_register[id] (line);
+ }
+ }
+ delete (line);
+ if (fclose (fp))
+ {
+ err_minor ("%s%s%s: %s",
+ PKGLOCALSTATEDIR, FILE_SEP_STR, _cfg_key[id],
+ strerror (errno));
+ }
+ }
+ }
+
+ fp = _cfg_fopen_conf (cfg_file_name);
+ if (fp)
+ {
+ size_t size = 0;
+ char *line = NULL;
+
+ while (num_of (_cfg_key) != (id = _cfg_getline (&line, &size, fp)))
+ {
+ log_info ("line: '%s'", line);
+
+ _cfg->active[id] = true;
+
+ if (0 != strcmp_c (line, _cfg_key[id]))
+ {
+ _cfg_register[id] (line);
+ }
+ }
+ delete (line);
+ if (fclose (fp))
+ {
+ err_minor ("%s: %s", cfg_file_name, strerror (errno));
+ }
+ }
+
+ setlocale (LC_CTYPE, lc_ctype);
+
+ /* For backwards compatibity with older configuration files.
+ */
+ if (cfg_has (_cfg, CFG_KEY_USB)
+ && 0 < list_size (_cfg->seen[CFG_KEY_INTERPRETER]))
+ {
+ log_info ("enabling interpreter support");
+ cfg_set (_cfg, CFG_KEY_INTERPRETER, true);
+ }
+
+ if (status) *status = s;
+ return _cfg;
+}
+
+/*! Releases resources acquired by a configuration object.
+
+ Always returns \c NULL.
+
+ \sa cfg_init
+ */
+void *
+cfg_exit (void *self)
+{
+ log_call ("(%p)", self);
+ require (_cfg == self);
+
+ if (_cfg)
+ {
+ int id;
+
+ for (id = 0; num_of (_cfg_key) > id; ++id)
+ {
+ if (_cfg->seen[id])
+ {
+ list_destroy (_cfg->seen[id], _cfg_dtor[id]);
+ _cfg->seen[id] = NULL;
+ }
+ }
+
+ delete (_cfg);
+ }
+ return _cfg;
+}
+
+
+/*! Searches for supported devices and adds them to a \a dev_list.
+ */
+void
+cfg_find (const void *self, cfg_key_id_type id, list *dev_list)
+{
+ log_call ("(%p, %u, %p)", self, id, dev_list);
+ require (_cfg && _cfg == self);
+ require (0 <= id && id < num_of (_cfg_key));
+
+ if (!cfg_has (self, id)) return;
+
+ _cfg_probe[id] (dev_list);
+}
+
+/*! Provides read-only access to the supported devices found.
+
+ \bug Access is not read-only.
+ */
+list *
+cfg_seen (const void *self, cfg_key_id_type id)
+{
+ log_call ("(%p, %u)", self, id);
+ require (_cfg && _cfg == self);
+ require (0 <= id && id < num_of (_cfg_key));
+
+ return _cfg->seen[id];
+}
+
+/*! Tells whether configuration for a key \a id is in effect.
+
+ \sa cfg_set
+ */
+bool
+cfg_has (const void *self, cfg_key_id_type id)
+{
+ log_call ("(%p, %u)", self, id);
+ require (_cfg && _cfg == self);
+ require (0 <= id && id < num_of (_cfg_key));
+
+ return _cfg->active[id];
+}
+
+/*! Enables or disabled support for a configuration key.
+
+ This can be used to disable configured functionality when the
+ required components are not available. Think network support
+ and interpreter based scanners.
+ */
+void
+cfg_set (void *self, cfg_key_id_type id, bool value)
+{
+ log_call ("(%p, %u, %u)", self, id, value);
+ require (_cfg && _cfg == self);
+ require (0 <= id && id < num_of (_cfg_key));
+
+ _cfg->active[id] = value;
+}
+
+
+bool
+cfg_has_value (const void *self, cfg_key_id_type id, const char* val)
+{
+ list *seen = cfg_seen (self, id);
+ const char *found = NULL;
+
+ if (val && seen)
+ {
+ list_entry *cur = seen->cur;
+ list_reset (seen);
+
+ while ((found = list_next (seen))
+ && 0 != strcmp_c (val, found))
+ {
+ /* condition does all the processing */
+ }
+ seen->cur = cur;
+ }
+ log_info ("check for %s in %s: %s", val, _cfg_key[id],
+ (NULL != found) ? "found" : "not found");
+ return (NULL != found);
+}
+
+
+/*! Returns the string that corresponds to the key \a id.
+ */
+cfg_key_type
+cfg_key (const void *self, cfg_key_id_type id)
+{
+ log_call ("(%p, %u)", self, id);
+ require (_cfg && _cfg == self);
+ require (0 <= id && id < num_of (_cfg_key));
+
+ return _cfg_key[id];
+}
+
+
+/*! Tries to find and open a configuration file.
+
+ Returns a valid file pointer if successfull, \c NULL otherwise.
+
+ \sa cfg_init
+ */
+static FILE *
+_cfg_fopen_conf (const char *name)
+{
+ const char *default_path = "." PATH_SEP_STR SANE_CONFIG_DIR_NAME;
+
+ char *path = getenv ("SANE_CONFIG_DIR");
+ char *next;
+ char *dir;
+ FILE *fp;
+
+ log_call ("(%s)", name);
+ require (name);
+
+ if (path)
+ {
+ size_t len = strlen (path);
+ char *p;
+
+ if (0 < len && PATH_SEP == path[len-1])
+ {
+ len += strlen (default_path);
+ }
+
+ p = t_malloc (len + 1, char);
+ if (p)
+ {
+ strcpy (p, path);
+ if (strlen (path) < len)
+ {
+ strcat (p, default_path);
+ }
+ path = strdup (p);
+ delete (p);
+ }
+ else
+ {
+ err_major ("SANE_CONFIG_DIR: %s", strerror (ENOMEM));
+ }
+ }
+ else
+ {
+ path = strdup (default_path);
+ }
+
+ if (!path)
+ {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ fp = NULL;
+ next = path;
+
+ while (!fp && (dir = strsep (&next, PATH_SEP_STR)))
+ {
+ fp = _cfg_fopen_data (dir, name);
+ }
+ delete (path);
+
+ return fp;
+}
+
+/*! Tries to open a configuration or data file.
+
+ Returns a valid file pointer if successfull, \c NULL otherwise.
+
+ \sa cfg_init
+ */
+static FILE *
+_cfg_fopen_data (const char *dir, const char *name)
+{
+ FILE *fp = NULL;
+ char file[PATH_MAX];
+ int n = snprintf (file, sizeof (file), "%s%c%s", dir, FILE_SEP, name);
+
+ log_call ("(%s, %s)", dir, name);
+ require (dir && name);
+
+ if (sizeof (file) > n)
+ {
+ fp = fopen (file, "rb");
+ if (!fp)
+ {
+ log_info ("%s: %s", file, strerror (errno));
+ }
+ }
+ else
+ {
+ err_minor ("%s%c%s: %s",
+ dir, FILE_SEP, name, strerror (ENAMETOOLONG));
+ }
+ if (fp) log_info ("using '%s'", file);
+
+ return fp;
+}
+
+/*! Returns a validated line of a configuration or data file.
+
+ Validated lines are guaranteed to start with one of the entries in
+ \c _cfg_key and adhere to the syntax for that particular key.
+
+ Comments and empty lines will be silently ignored, invalid entries
+ will be logged and skipped and leading and/or trailing white space
+ as well as trailing comments will be stripped.
+
+ Comments start with a sharp, \c #. An escape mechanism is \e not
+ supported.
+
+ Returns \c _CFG_KEY_ID_TERMINATOR_ if no validated line can be
+ found.
+ */
+static cfg_key_id_type
+_cfg_getline (char **line, size_t *size, FILE *fp)
+{
+ int id = num_of (_cfg_key);
+ bool valid = false;
+ ssize_t n;
+
+ char *lc_ctype;
+
+ require (line && size && fp);
+
+ lc_ctype = setlocale (LC_CTYPE, "C");
+
+ while (!valid && -1 != (n = getline (line, size, fp)))
+ {
+ char *s;
+
+ log_data ("looking at '%s'", *line);
+
+ s = strchr (*line, '#');
+ if (s) *s = '\0'; /* chomp trailing comments */
+
+ s = *line;
+ n = strlen (s);
+
+ while (0 < n && (isspace (*s))) /* whitespace removal */
+ --n, ++s;
+ while (0 < n && (isspace (s[n-1])))
+ --n, s[n] = '\0';
+
+ log_data ("payload is '%s'", s);
+
+ require (strlen (s) == n);
+
+ if (0 < n) /* content validation */
+ {
+ id = 0;
+ while (num_of (_cfg_key) > id
+ && 0 != strncmp_c (s, _cfg_key[id], strlen (_cfg_key[id])))
+ {
+ log_data ("%s !~ %s", _cfg_key[id], s);
+ ++id;
+ }
+
+ valid = (num_of (_cfg_key) > id) && _cfg_validate[id] (s);
+ if (valid && s != *line)
+ {
+ memmove (*line, s, strlen (s) + 1);
+ }
+ if (!valid)
+ {
+ err_major ("invalid: '%s'", s);
+ }
+ }
+ }
+
+ setlocale (LC_CTYPE, lc_ctype);
+
+ return (-1 == n ? num_of (_cfg_key) : id);
+}
+
+/*! Tells whether a \a string consists of a single key.
+ */
+static bool
+_cfg_is_bare_key (const char *string)
+{
+ int id = 0;
+
+ require (string);
+
+ while (num_of (_cfg_key) > id
+ && 0 != strcmp_c (string, _cfg_key[id]))
+ {
+ ++id;
+ }
+ return (num_of (_cfg_key) > id);
+}
+
+/*! Says whether a \a string makes up a valid network device entry.
+ *
+ * Valid network device entries have one of the following formats:
+ * - \c net IPv4-address[( |:)port]
+ * - \c net hostname[( |:)port]
+ *
+ * \todo Tighten up validation!
+ * \todo Consider support for IPv4-address ranges by use of netmasks
+ * or CIDR notation.
+ */
+static bool
+_cfg_is_valid_net_entry (const char *string)
+{
+ const char *p = string;
+
+ int port = 0;
+ char c = '\0';
+
+ require (string);
+
+ if (0 == strcmp_c (string, _cfg_key[CFG_KEY_NET]))
+ return false;
+
+ if (0 != strncmp_c (string, _cfg_key[CFG_KEY_NET],
+ strlen (_cfg_key[CFG_KEY_NET]))
+ || !isspace (string[strlen(_cfg_key[CFG_KEY_NET])]))
+ return false;
+
+ p += strlen (_cfg_key[CFG_KEY_NET]);
+ while (*p && isspace (*p))
+ {
+ ++p;
+ }
+
+ if (1 == sscanf (p, "%*s %d%1s", &port, &c)) return true;
+
+ // check for [ip/host]:[port] format
+ while (*p && !isspace (*p) && ':' != *p)
+ {
+ ++p;
+ }
+ if ('\0' == *p) return true; // case without a port number
+ if (isspace (*p)) return false;
+ if (1 == sscanf (p, ":%d%1s", &port, &c)) return true;
+
+ return false;
+}
+
+/*! Says whether a \a string is a valid SCSI device entry.
+ *
+ * Valid SCSI device entries have the following format:
+ * - \c scsi[ vendor[ model]]
+ *
+ * Vendor and model specifications are case-insensitive.
+ *
+ * \todo Consider support for absolute path names to a device.
+ */
+static bool
+_cfg_is_valid_scsi_entry (const char *string)
+{
+ const char *p = string;
+
+ require (string);
+
+ if (0 == strcmp_c (string, _cfg_key[CFG_KEY_SCSI]))
+ return true;
+
+ if (0 != strncmp_c (string, _cfg_key[CFG_KEY_SCSI],
+ strlen (_cfg_key[CFG_KEY_SCSI]))
+ || !isspace (string[strlen(_cfg_key[CFG_KEY_SCSI])]))
+ return false;
+
+ p += strlen (_cfg_key[CFG_KEY_SCSI]);
+ while (*p && isspace (*p))
+ {
+ ++p;
+ }
+ while (*p && !isspace (*p))
+ {
+ ++p;
+ }
+ if (!*p) return true; /* vendor only */
+
+ while (*p && isspace (*p))
+ {
+ ++p;
+ }
+ while (*p && !isspace (*p))
+ {
+ ++p;
+ }
+ if (!*p) return true; /* vendor model specification */
+
+ return false; /* we have trailing cruft */
+}
+
+/*! Decides whether a \a string constitutes a valid USB entry.
+ *
+ * Valid USB entries have the following format:
+ * - \c usb[ vendor_id prodoct_id]
+ *
+ * Both IDs, if present, shall be \c 0x or \c 0X prefixed sequences
+ * of one to four hexadecimal digits. Case of the digits \c A thru
+ * \c F is irrelevant and may be mixed, even within a single ID.
+ *
+ * \bug Does not catch IDs without intervening whitespace.
+ */
+static bool
+_cfg_is_valid_usb_entry (const char *string)
+{
+ unsigned int vendor;
+ unsigned int product;
+ char x[] = "x"; /* matches 'x' or 'X' */
+ char c = '\0'; /* matches trailing content */
+
+ require (string);
+
+ if (0 == strcmp_c (string, _cfg_key[CFG_KEY_USB]))
+ return true;
+
+ if (0 != strncmp_c (string, _cfg_key[CFG_KEY_USB],
+ strlen (_cfg_key[CFG_KEY_USB]))
+ || !isspace (string[strlen(_cfg_key[CFG_KEY_USB])]))
+ return false;
+
+ return (4 == sscanf (string, "%*s 0%1[xX]%4x 0%1[xX]%4x%1s",
+ x, &vendor, x, &product, &c));
+}
+
+/*! Decides whether a \a string constitutes a valid interpreter entry.
+ *
+ * Valid interpreter entries have the following format:
+ * - \c interpreter usb vendor_id prodoct_id library[ firmware-file]
+ *
+ * Both IDs, if present, shall be \c 0x or \c 0X prefixed sequences
+ * of one to four hexadecimal digits. Case of the digits \c A thru
+ * \c F is irrelevant and may be mixed, even within a single ID.
+ *
+ * \bug Does not catch IDs without intervening whitespace.
+ */
+static bool
+_cfg_is_valid_interpreter_entry (const char *string)
+{
+ const char *p1 = string;
+ const char *p2 = _cfg_key[CFG_KEY_INTERPRETER];
+
+ unsigned int vendor;
+ unsigned int product;
+ char x[] = "x"; /* matches 'x' or 'X' */
+ char c1 = '\0'; /* matches required whitespace */
+ char c2 = '\0'; /* matches start of library-name */
+
+ require (string);
+
+ if (0 != strncmp_c (p1, p2, strlen (p2)))
+ return false;
+
+ p1 += strlen (p2);
+ while (*p1 && isspace (*p1))
+ {
+ ++p1;
+ }
+
+ p2 = _cfg_key[CFG_KEY_USB];
+ if (0 != strncmp_c (p1, p2, strlen (p2))
+ || !isspace (p1[strlen(p2)]))
+ return false;
+
+ return (6 == sscanf (string, "%*s %*s 0%1[xX]%4x 0%1[xX]%4x%c %c",
+ x, &vendor, x, &product, &c1, &c2)
+ && isspace (c1));
+}
+
+/*! Says whether \a string is a valid generic key/value entry.
+ *
+ * Valid entries have the following format:
+ * - \c <key> <value>
+ */
+static bool
+_cfg_is_valid_key_value_entry (cfg_key_id_type key_id, const char *string)
+{
+ require (string);
+
+ if (0 != strncmp_c (string, _cfg_key[key_id],
+ strlen (_cfg_key[key_id]))
+ || !isspace (string[strlen(_cfg_key[key_id])]))
+ return false;
+
+ return true;
+}
+
+/*! Says whether \a string is a valid FS blacklist entry.
+ *
+ * Valid blacklist entries have the following format:
+ * - \c fs-blacklist fw_name
+ */
+static bool
+_cfg_is_valid_fs_blacklist_entry (const char *string)
+{
+ return _cfg_is_valid_key_value_entry (CFG_KEY_FS_BLACKLIST, string);
+}
+
+/*! Says whether \a string is a valid option entry.
+ *
+ * Valid option entries have the following format:
+ * - \c option option_name
+ */
+static bool
+_cfg_is_valid_option_entry (const char *string)
+{
+ return _cfg_is_valid_key_value_entry (CFG_KEY_OPTION, string);
+}
+
+/*! Does \e not register a \a string at all.
+
+ This function should never be called at run-time. It is solely
+ meant as a place holder for use in the \c _cfg_register array.
+
+ Always returns \c false.
+ */
+static bool
+_cfg_register_no_op (const char *string)
+{
+ require (string);
+
+ err_minor ("internal error: '%s'", string);
+ return false;
+}
+
+static bool
+_cfg_register_net_entry (const char *string)
+{
+ cfg_net_info *info = NULL;
+
+ require (string);
+
+ if (!_cfg->seen[CFG_KEY_NET])
+ _cfg->seen[CFG_KEY_NET] = list_create ();
+ if (!_cfg->seen[CFG_KEY_NET]) return false;
+
+ info = t_malloc (1, cfg_net_info);
+ if (info)
+ {
+ char *spec = NULL;
+ int port = 0;
+
+ string += strlen (_cfg_key[CFG_KEY_NET]);
+ while (++string && isspace (*string))
+ {
+ /* condition does all the processing */
+ }
+ spec = strdup (string);
+
+ if (1 == sscanf (spec, "%*s %d", &port))
+ {
+ char *colon;
+ char *p = spec;
+ while (*p && !isspace (*p))
+ {
+ ++p;
+ }
+ *p = ':';
+ colon = p;
+ ++p;
+ while (*p && isspace (*p))
+ {
+ ++p;
+ }
+ memmove (colon + 1, p, strlen (p) + 1);
+ }
+
+ if (list_append (_cfg->seen[CFG_KEY_NET], info))
+ {
+ info->spec = spec;
+ log_info ("registered '%s'", info->spec);
+ }
+ else
+ {
+ delete (spec);
+ delete (info);
+ }
+ }
+
+ return (NULL != info);
+}
+
+/*! Attemps to register supported SCSI device information.
+ */
+static bool
+_cfg_register_scsi_entry (const char *string)
+{
+ cfg_scsi_info *info = NULL;
+
+ require (string);
+
+ if (!_cfg->seen[CFG_KEY_SCSI])
+ _cfg->seen[CFG_KEY_SCSI] = list_create ();
+ if (!_cfg->seen[CFG_KEY_SCSI]) return false;
+
+ info = t_malloc (1, cfg_scsi_info);
+ if (info)
+ {
+ char *vendor = NULL;
+ char *model = NULL;
+
+ sscanf (string, "%*s %as %as", &vendor, &model);
+
+ if (list_append (_cfg->seen[CFG_KEY_SCSI], info))
+ {
+ info->vendor = vendor;
+ info->model = model;
+ log_info ("registered '%s'", string);
+ }
+ else
+ {
+ delete (vendor);
+ delete (model);
+ delete (info);
+ }
+ }
+
+ return (NULL != info);
+}
+
+/*! Attempts to register information for a USB entry.
+ *
+ * Relies on the assumption that the \a string has been validated and
+ * returns \c true if successfull. When not successful, this will be
+ * caused by lack of memory and the function returns \c false.
+ */
+static bool
+_cfg_register_usb_entry (const char *string)
+{
+ cfg_usb_info *info = NULL;
+
+ require (string);
+
+ if (!_cfg->seen[CFG_KEY_USB])
+ _cfg->seen[CFG_KEY_USB] = list_create ();
+ if (!_cfg->seen[CFG_KEY_USB]) return false;
+
+ info = t_malloc (1, cfg_usb_info);
+ if (info)
+ {
+ unsigned int vendor;
+ unsigned int product;
+
+ sscanf (string, "%*s %x %x", &vendor, &product);
+
+ if (list_append (_cfg->seen[CFG_KEY_USB], info))
+ {
+ info->vendor = vendor;
+ info->product = product;
+ log_info ("registered '%s'", string);
+ }
+ else
+ {
+ delete (info);
+ }
+ }
+
+ return (NULL != info);
+}
+
+/*! Tries to register information for an interpreter entry.
+ *
+ * \todo Check for existence of the \a library and \a firmware file.
+ */
+static bool
+_cfg_register_interpreter_entry (const char *string)
+{
+ cfg_interpreter_info *info = NULL;
+
+ require (string);
+
+ if (!_cfg->seen[CFG_KEY_INTERPRETER])
+ _cfg->seen[CFG_KEY_INTERPRETER] = list_create ();
+ if (!_cfg->seen[CFG_KEY_INTERPRETER]) return false;
+
+ info = t_malloc (1, cfg_interpreter_info);
+ if (info)
+ {
+ unsigned int vendor;
+ unsigned int product;
+ char *library = NULL;
+ char *firmware = NULL;
+
+ sscanf (string, "%*s %*s %x %x %as %as",
+ &vendor, &product, &library, &firmware);
+
+ if (library && _cfg_have_interpreter (library, firmware)
+ && list_append (_cfg->seen[CFG_KEY_INTERPRETER], info))
+ {
+ info->vendor = vendor;
+ info->product = product;
+ info->library = library;
+ info->firmware = firmware;
+ log_info ("registered '%s'", string);
+ }
+ else
+ {
+ delete (library);
+ delete (firmware);
+ delete (info);
+ }
+ }
+ return (NULL != info);
+}
+
+/*! Registers information for a generic key/value entry
+ */
+static bool
+_cfg_register_key_value_entry (cfg_key_id_type key_id, const char *string)
+{
+ char *value = NULL;
+
+ require (string);
+
+ if (!_cfg->seen[key_id])
+ _cfg->seen[key_id] = list_create ();
+ if (!_cfg->seen[key_id]) return false;
+
+ string += strlen (_cfg_key[key_id]);
+ while (++string && isspace (*string))
+ {
+ /* condition does all the processing */
+ }
+ value = strdup (string);
+
+ if (!list_append (_cfg->seen[key_id], value))
+ {
+ delete (value);
+ }
+ if (NULL != value)
+ {
+ log_info ("registered '%s %s'", _cfg_key[key_id], value);
+ }
+ return (NULL != value);
+}
+
+/*! Registers information for an FS command blacklist entry
+ */
+static bool
+_cfg_register_fs_blacklist_entry (const char *string)
+{
+ return _cfg_register_key_value_entry (CFG_KEY_FS_BLACKLIST, string);
+}
+
+/*! Registers information for an option entry
+ */
+static bool
+_cfg_register_option_entry (const char *string)
+{
+ int i = 0;
+ bool found = false;
+ const char* value = NULL;
+
+ value = string + strlen (_cfg_key[CFG_KEY_OPTION]);
+ while (++value && isspace (*value))
+ {
+ /* condition does all the processing */
+ }
+
+ for (i=0; i<num_of (_opt_vals); ++i)
+ {
+ if (0 == strcmp_c (value, _opt_vals[i]))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ {
+ log_info ("unknown option: '%s'", value);
+ return false;
+ }
+ return _cfg_register_key_value_entry (CFG_KEY_OPTION, string);
+}
+
+/*!
+ */
+static void
+_cfg_probe_no_op (list *dev_list)
+{
+ return;
+}
+
+/* Reuse the SCSI attach function because it does all we need to do,
+ at least for as long as it caters to the sanei_scsi API.
+ */
+#define _cfg_net_attach _cfg_scsi_attach
+
+/*!
+ */
+static void
+_cfg_probe_net (list *dev_list)
+{
+ char* buf = NULL;
+ char* rbuf = NULL;
+
+ size_t size = 0;
+
+ ssize_t n = 0;
+ uint16_t id = 0;
+ uint8_t status;
+
+ void* net = NULL;
+ int sock = -1;
+
+ int i = 0;
+ char* cp;
+
+ list *registry = _cfg->seen[CFG_KEY_NET];
+ cfg_net_info *info = NULL;
+ list_entry *cur;
+
+ require (dev_list);
+ if (!registry) return;
+
+ net = net_init (NULL, NULL);
+ if (net) sock = net_get_sock (net);
+
+ if (!net || 0 > sock)
+ {
+ cfg_set (_cfg, CFG_KEY_NET, false);
+ return;
+ }
+
+ /* construct string from list of scanners */
+
+ cur = registry->cur;
+ list_reset (registry);
+ while ((info = list_next (registry)))
+ {
+ /* +1 for separator */
+ size += strlen (info->spec) + 1;
+ }
+ registry->cur = cur;
+
+ buf = t_malloc (size+1, char); /* +1 for NULL termination character */
+ if (!buf)
+ {
+ cfg_set (_cfg, CFG_KEY_NET, false);
+ return;
+ }
+ memset (buf, 0, size+1);
+
+ cur = registry->cur;
+ list_reset (registry);
+ while ((info = list_next (registry)))
+ {
+ strcat (buf, info->spec);
+ strcat (buf, "\n");
+ }
+ registry->cur = cur;
+
+ log_info ("Probe network:\n%s", buf);
+
+ /* send string of NULL separated scanner ips to the network plugin */
+ for (i=0; i<size; ++i) if ('\n' == buf[i]) buf[i] = '\0';
+ n = ipc_send (sock, 0, TYPE_LIST, size, buf);
+ delete (buf);
+
+ if (n != size)
+ {
+ log_info ("Communication error occurred. Disabling network plugin.");
+ cfg_set (_cfg, CFG_KEY_NET, false);
+ return;
+ }
+
+ /* receive a string of available network scanners */
+ n = -1;
+ int tries = 3;
+ while (0 > n && 0 < tries)
+ {
+ n = ipc_recv (sock, &id, &status, (void **) &rbuf);
+ --tries;
+ }
+
+ /* bail if no network scanners were found or something bad happened */
+ if (0 >= n || 0 == strlen (rbuf) || status != STATUS_OK)
+ {
+ log_info ("No network scanners detected. Disabling network plugin.");
+ cfg_set (_cfg, CFG_KEY_NET, false);
+ delete (rbuf);
+ return;
+ }
+
+ /* process the scanner listing string */
+ _cfg_dev_list = dev_list;
+ _cfg_dev_key = _cfg_key[CFG_KEY_NET];
+
+ cp = rbuf;
+ for (i=0; i<n; ++i)
+ {
+ if ('\0' == rbuf[i])
+ {
+ log_info ("Detected network scanner: %s", cp);
+ _cfg_net_attach (cp);
+ cp = rbuf + i + 1;
+ }
+ }
+
+ _cfg_dev_key = NULL;
+ _cfg_dev_list = NULL;
+
+ delete (rbuf);
+
+ return;
+}
+
+/*!
+ */
+static void
+_cfg_probe_scsi (list *dev_list)
+{
+ list *registry = _cfg->seen[CFG_KEY_SCSI];
+ cfg_scsi_info *info = NULL;
+ list_entry *cur;
+
+ require (dev_list);
+ if (!registry) return;
+
+ cur = registry->cur;
+ list_reset (registry);
+ while ((info = list_next (registry)))
+ {
+ _cfg_dev_list = dev_list;
+ _cfg_dev_key = _cfg_key[CFG_KEY_SCSI];
+ sanei_scsi_find_devices (info->vendor, info->model, NULL,
+ -1, -1, -1, -1, _cfg_scsi_attach);
+ _cfg_dev_key = NULL;
+ _cfg_dev_list = NULL;
+ }
+ registry->cur = cur;
+
+ return;
+}
+
+static void
+_cfg_probe_usb (list *dev_list)
+{
+ list *registry = _cfg->seen[CFG_KEY_USB];
+ cfg_usb_info *info = NULL;
+ list_entry *cur;
+
+ require (dev_list);
+ if (!registry) return;
+
+ cur = registry->cur;
+ list_reset (registry);
+ while ((info = list_next (registry)))
+ {
+ _cfg_dev_list = dev_list;
+ _cfg_dev_key = _cfg_key[CFG_KEY_USB];
+ sanei_usb_find_devices (info->vendor, info->product, _cfg_usb_attach);
+ _cfg_dev_key = NULL;
+ _cfg_dev_list = NULL;
+ }
+ registry->cur = cur;
+
+ return;
+}
+
+/*!
+ */
+static void
+_cfg_probe_interpreter (list *dev_list)
+{
+ list *registry = _cfg->seen[CFG_KEY_INTERPRETER];
+ cfg_interpreter_info *info = NULL;
+ list_entry *cur;
+
+ require (dev_list);
+ if (!registry) return;
+
+ cur = registry->cur;
+ list_reset (registry);
+ while ((info = list_next (registry)))
+ {
+ _cfg_dev_list = dev_list;
+ _cfg_dev_key = _cfg_key[CFG_KEY_INTERPRETER];
+ sanei_usb_find_devices (info->vendor, info->product, _cfg_usb_attach);
+ _cfg_dev_key = NULL;
+ _cfg_dev_list = NULL;
+ }
+ registry->cur = cur;
+
+ return;
+}
+
+static SANE_Status
+_cfg_scsi_attach (SANE_String_Const dev_name)
+{
+ SANE_String name = NULL;
+ size_t len_name = (strlen (_cfg_dev_key) + strlen (DEV_NAME_SEP_STR)
+ + strlen (dev_name) + 1);
+
+ name = t_malloc (len_name, SANE_Char);
+ if (name)
+ {
+ SANE_Status s = SANE_STATUS_GOOD;
+
+ strcpy (name, _cfg_dev_key);
+ strcat (name, DEV_NAME_SEP_STR);
+ strcat (name, dev_name);
+
+ s = _cfg_attach (name, _cfg_dev_list);
+ if (SANE_STATUS_NO_MEM == s)
+ {
+ delete (name);
+ }
+ return s;
+ }
+ return SANE_STATUS_NO_MEM;
+}
+
+static SANE_Status
+_cfg_usb_attach (SANE_String_Const dev_name)
+{
+ const char *sanei_usb_prefix = "libusb:";
+
+ SANE_String name = NULL;
+ size_t len_name = (strlen (_cfg_dev_key) + strlen (DEV_NAME_SEP_STR)
+ + strlen (dev_name) + 1);
+
+ if (0 == strncmp_c (dev_name, sanei_usb_prefix, strlen (sanei_usb_prefix)))
+ {
+ len_name -= strlen (sanei_usb_prefix);
+ dev_name += strlen (sanei_usb_prefix);
+ }
+
+ name = t_malloc (len_name, SANE_Char);
+ if (name)
+ {
+ SANE_Status s = SANE_STATUS_GOOD;
+
+ strcpy (name, _cfg_dev_key);
+ strcat (name, DEV_NAME_SEP_STR);
+ strcat (name, dev_name);
+
+ s = _cfg_attach (name, _cfg_dev_list);
+ if (SANE_STATUS_NO_MEM == s)
+ {
+ delete (name);
+ }
+ return s;
+ }
+ return SANE_STATUS_NO_MEM;
+}
+
+/*! Creates a SANE_Device and attaches it to the \a dev_list.
+ *
+ * While perhaps not particularly user-friendly, it is perfectly okay
+ * to attach a SANE_Device with the vendor, model and type fields set
+ * to something that does not exactly correspond with the device that
+ * is dangling on the other end of the connection.
+ *
+ * We may not be able to get reliable information for several reasons,
+ * not in the least because of (intermittent?) failure to communicate
+ * with the device.
+ *
+ * Returns SANE_STATUS_NO_MEM if a SANE_Device could not be created
+ * and added to the \a dev_list, SANE_STATUS_GOOD otherwise. Note
+ * that in the latter case only the name field is guaranteed to be
+ * not \c NULL.
+ */
+static SANE_Status
+_cfg_attach (SANE_String_Const dev_name, list *dev_list)
+{
+ SANE_Device *dev = t_malloc (1, SANE_Device);
+
+ require (dev_name);
+
+ if (!dev || !list_append (dev_list, dev))
+ {
+ delete (dev);
+ return SANE_STATUS_NO_MEM;
+ }
+
+ dev->name = dev_name;
+
+ dev->vendor = _cfg_get_vendor (dev_name);
+ dev->model = _cfg_get_model (dev_name);
+ dev->type = _cfg_get_type (dev_name);
+
+ return SANE_STATUS_GOOD;
+}
+
+/*! Returns a best effort vendor name for a device.
+ *
+ * \todo Remove hard-coding (as soon as we support non-Epson
+ * devices).
+ */
+static SANE_String_Const
+_cfg_get_vendor (SANE_String_Const dev_name)
+{
+ return strdup ("Epson");
+}
+
+/*! Returns a best effort model name for a device.
+ */
+static SANE_String_Const
+_cfg_get_model (SANE_String_Const dev_name)
+{
+ SANE_String_Const model = NULL;
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ char *fw_name = NULL;
+ channel *ch = NULL;
+
+ require (dev_name);
+
+ ch = channel_create (dev_name, &status);
+ if (!ch || SANE_STATUS_GOOD != status)
+ {
+ err_minor ("%s", sane_strstatus (status));
+ }
+ else
+ {
+ ch->open (ch, &status);
+ if (SANE_STATUS_GOOD == status)
+ {
+ fw_name = get_fw_name (ch);
+ }
+ ch->close (ch, NULL);
+ ch = ch->dtor (ch);
+ }
+
+ log_info ("F/W name: '%s'", fw_name);
+
+ model = model_info_cache_get_model (fw_name);
+ delete (fw_name);
+
+ return model;
+}
+
+/*! Returns a best effort type description for a device.
+ *
+ * \todo Figure out how to deal with the predefined strings in the
+ * face of devices that have a flatbed and ADF and/or TPU.
+ * Then there is also the various ideas people have about
+ * multi-function peripherals. Are these SPC's, all-in-ones,
+ * MFPPs or even plain scanners with both a flatbed and ADF?
+ */
+static SANE_String_Const
+_cfg_get_type (SANE_String_Const dev_name)
+{
+ return strdup ("flatbed scanner");
+}
+
+/*! Divines whether a certain interpreter is usable.
+ */
+static bool
+_cfg_have_interpreter (const char *library, const char *firmware)
+{
+ require (library);
+
+ return true;
+}
+
+
+static void
+_cfg_net_dtor (void *self)
+{
+ cfg_net_info *p = self;
+
+ if (!p) return;
+
+ const_delete (p->spec, char *);
+ delete (p);
+}
+
+static void
+_cfg_scsi_dtor (void *self)
+{
+ cfg_scsi_info *p = self;
+
+ if (!p) return;
+
+ const_delete (p->vendor, char *);
+ const_delete (p->model , char *);
+ delete (p);
+}
+
+static void
+_cfg_interpreter_dtor (void *self)
+{
+ cfg_interpreter_info *p = self;
+
+ if (!p) return;
+
+ const_delete (p->library , char *);
+ const_delete (p->firmware, char *);
+ delete (p);
+}
diff --git a/backend/cfg-obj.h b/backend/cfg-obj.h
new file mode 100644
index 0000000..1e414fa
--- /dev/null
+++ b/backend/cfg-obj.h
@@ -0,0 +1,151 @@
+/* cfg-obj.h -- configuration objects
+ * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifndef included_cfg_obj_h
+#define included_cfg_obj_h
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdint.h>
+
+#include <sane/sane.h>
+
+#include "list.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ extern const char *cfg_file_name;
+
+ typedef enum
+ {
+ CFG_KEY_NET = 0,
+ CFG_KEY_PIO,
+ CFG_KEY_SCSI,
+ CFG_KEY_USB,
+
+ CFG_KEY_INTERPRETER,
+ CFG_KEY_FS_BLACKLIST,
+
+ CFG_KEY_OPTION,
+
+ _CFG_KEY_ID_TERMINATOR_
+ }
+ cfg_key_id_type;
+
+ typedef const char * cfg_key_type;
+
+ typedef struct
+ {
+ const char *spec;
+
+ } cfg_net_info;
+
+ typedef struct
+ {
+ const char *vendor;
+ const char *model;
+
+ } cfg_scsi_info;
+
+ typedef struct
+ {
+ uint16_t vendor;
+ uint16_t product;
+
+ } cfg_usb_info;
+
+ typedef struct
+ {
+ uint16_t vendor;
+ uint16_t product;
+ const char *library;
+ const char *firmware;
+
+ } cfg_interpreter_info;
+
+
+ void * cfg_init (const char *pkgdatadir, SANE_Status *status);
+ void * cfg_exit (void *self);
+
+ void cfg_find (const void *self, cfg_key_id_type id, list *dev_list);
+ list * cfg_seen (const void *self, cfg_key_id_type id);
+
+ bool cfg_has (const void *self, cfg_key_id_type id);
+ void cfg_set (void *self, cfg_key_id_type id, bool value);
+
+ bool cfg_has_value (const void *self, cfg_key_id_type id, const char* val);
+
+ cfg_key_type cfg_key (const void *self, cfg_key_id_type id);
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* !defined (included_cfg_obj_h) */
diff --git a/backend/channel-net.c b/backend/channel-net.c
new file mode 100644
index 0000000..38439fd
--- /dev/null
+++ b/backend/channel-net.c
@@ -0,0 +1,242 @@
+/* channel_net.c -- network device communication channel
+ * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+/*! \file
+ * \brief Implements a network device communication channel.
+ * \todo Deal correctly with \c SIGPIPE.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "channel.h"
+
+#include <string.h>
+#include <unistd.h>
+
+#include "ipc.h"
+#include "net-obj.h"
+#include "utils.h"
+#include "hw-data.h"
+
+
+static void channel_net_open (channel *, SANE_Status *);
+static void channel_net_close (channel *, SANE_Status *);
+
+static ssize_t channel_net_send (channel *, const void *,
+ size_t, SANE_Status *);
+static ssize_t channel_net_recv (channel *, void *,
+ size_t, SANE_Status *);
+
+
+channel *
+channel_net_ctor (channel *self, const char *dev_name, SANE_Status *status)
+{
+ log_call ("(%p, '%s', %p)", self, dev_name, status);
+
+ if (status) *status = SANE_STATUS_GOOD;
+
+ require (self && dev_name);
+ require (0 == strncmp_c (dev_name, "net:", strlen ("net:")));
+
+ self->name = strdup (dev_name);
+ if (!self->name)
+ {
+ if (status) *status = SANE_STATUS_NO_MEM;
+ return self->dtor (self);
+ }
+
+ self->open = channel_net_open;
+ self->close = channel_net_close;
+
+ self->send = channel_net_send;
+ self->recv = channel_net_recv;
+
+ return self;
+}
+
+/*! Each call must consist of a complete handshake step.
+ * Cannot send in parts.
+ */
+static ssize_t
+channel_net_send (channel *self, const void *buffer,
+ size_t size, SANE_Status *status)
+{
+ ssize_t n = 0;
+ if (0 > self->fd)
+ {
+ if (status) *status = SANE_STATUS_IO_ERROR;
+ return -1;
+ }
+ if (status) *status = SANE_STATUS_GOOD;
+
+ require (self && buffer);
+ require (0 < self->id);
+
+ n = ipc_send (self->fd, self->id, TYPE_ESC, size, buffer);
+ if (n != size)
+ {
+ if (status) *status = SANE_STATUS_IO_ERROR;
+ }
+ return n;
+}
+
+/*! Each call must consist of a complete handshake step.
+ * Cannot receive in parts.
+ */
+static ssize_t
+channel_net_recv (channel *self, void *buffer,
+ size_t size, SANE_Status *status)
+{
+ char* rbuf = NULL;
+ uint16_t id = 0;
+ uint8_t ipc_status = STATUS_OK;
+ ssize_t n = 0;
+
+ if (0 > self->fd)
+ {
+ if (status) *status = SANE_STATUS_IO_ERROR;
+ return -1;
+ }
+ if (status) *status = SANE_STATUS_GOOD;
+
+ require (self && buffer);
+ require (0 < self->id);
+
+ n = ipc_recv (self->fd, &id, &ipc_status, (void**)&rbuf);
+ if (n != size) err_major ("expected %zd bytes, received %zd bytes", size, n);
+ if (!rbuf || id != self->id || STATUS_OK != ipc_status || n != size)
+ {
+ if (status) *status = SANE_STATUS_IO_ERROR;
+ delete (rbuf);
+ return -1;
+ }
+
+ memcpy (buffer, rbuf, n);
+ delete (rbuf);
+ return n;
+}
+
+static void
+channel_net_open (channel *self, SANE_Status *status)
+{
+ void* net = NULL;
+ ssize_t n = 0;
+
+ uint8_t ipc_status = STATUS_OK;
+
+ char* scanner = self->name + strlen ("net:");
+
+ if (status) *status = SANE_STATUS_GOOD;
+
+ net = net_init ("", NULL);
+ if (net) self->fd = net_get_sock (net);
+ if (!net || 0 > self->fd)
+ {
+ if (status) *status = SANE_STATUS_IO_ERROR;
+ return;
+ }
+
+ n = ipc_send (self->fd, 0, TYPE_OPEN, strlen (scanner), scanner);
+ if (n != strlen (scanner))
+ {
+ self->fd = -1;
+ if (status) *status = SANE_STATUS_IO_ERROR;
+ return;
+ }
+
+ n = ipc_recv (self->fd, &self->id, &ipc_status, NULL);
+
+ if (STATUS_OK != ipc_status || 0 != n)
+ {
+ self->id = 0;
+ self->fd = -1;
+ if (status) *status = SANE_STATUS_IO_ERROR;
+ return;
+ }
+
+ log_info ("Opened network scanner at: %s", scanner);
+}
+
+static void
+channel_net_close (channel *self, SANE_Status *status)
+{
+ ssize_t n = 0;
+
+ if (status) *status = SANE_STATUS_GOOD;
+
+ n = ipc_send (self->fd, self->id, TYPE_CLOSE, 0, NULL);
+ self->id = 0;
+ self->fd = -1;
+ if (0 != n)
+ {
+ if (status) *status = SANE_STATUS_IO_ERROR;
+ log_info ("failed to close network scanner: %s",
+ self->name + strlen ("net:"));
+ return;
+ }
+ log_info ("closed network scanner: %s", self->name + strlen ("net:"));
+}
diff --git a/backend/channel-pio.c b/backend/channel-pio.c
new file mode 100644
index 0000000..c4f202c
--- /dev/null
+++ b/backend/channel-pio.c
@@ -0,0 +1,165 @@
+/* channel_pio.c -- parallel device communication channel
+ * Copyright (C) 2008, 2009, 2013 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+/*! \file
+ \brief Implements a parallel device communication channel.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "channel.h"
+
+#include <string.h>
+
+#include "utils.h"
+
+/* Deprecated includes */
+#include "sane/sanei_pio.h"
+
+
+static void channel_pio_open (channel *, SANE_Status *);
+static void channel_pio_close (channel *, SANE_Status *);
+
+static ssize_t channel_pio_send (channel *, const void *,
+ size_t, SANE_Status *);
+static ssize_t channel_pio_recv (channel *, void *,
+ size_t, SANE_Status *);
+
+static void channel_pio_set_max_request_size (channel *, size_t);
+
+channel *
+channel_pio_ctor (channel *self, const char *dev_name, SANE_Status *status)
+{
+ require (self && dev_name);
+ require (0 == strncmp_c (dev_name, "pio:", strlen ("pio:")));
+
+ self->open = channel_pio_open;
+ self->close = channel_pio_close;
+
+ self->send = channel_pio_send;
+ self->recv = channel_pio_recv;
+
+ self->set_max_request_size = channel_pio_set_max_request_size;
+
+ if (status) *status = SANE_STATUS_UNSUPPORTED;
+ return self->dtor (self);
+}
+
+static ssize_t
+channel_pio_send (channel *self, const void *buffer,
+ size_t size, SANE_Status *status)
+{
+ SANE_Status s = SANE_STATUS_GOOD;
+ ssize_t n = sanei_pio_write (self->fd, buffer, size);
+
+ if (size != n)
+ s = SANE_STATUS_INVAL;
+ if (status) *status = s;
+
+ return n;
+}
+
+static ssize_t
+channel_pio_recv (channel *self, void *buffer,
+ size_t size, SANE_Status *status)
+{
+ SANE_Status s = SANE_STATUS_GOOD;
+ ssize_t n = sanei_pio_read (self->fd, buffer, (size_t) size);
+
+ if (size != n)
+ s = SANE_STATUS_INVAL;
+ if (status) *status = s;
+
+ return n;
+}
+
+static void
+channel_pio_open (channel *self, SANE_Status *status)
+{
+ SANE_Status s = sanei_pio_open (self->name, &self->fd);
+
+ if (SANE_STATUS_GOOD != s)
+ {
+ err_fatal ("can not open %s (%s)", self->name, sane_strstatus (s));
+ }
+ if (status) *status = s;
+}
+
+static void
+channel_pio_close (channel *self, SANE_Status *status)
+{
+ sanei_pio_close (self->fd);
+ self->fd = -1;
+}
+
+static void
+channel_pio_set_max_request_size (channel *self, size_t size)
+{
+ require (self);
+
+ self->max_size = (size < (32 * 1024)) ? size : (32 * 1024);
+}
diff --git a/backend/channel-scsi.c b/backend/channel-scsi.c
new file mode 100644
index 0000000..20c9e93
--- /dev/null
+++ b/backend/channel-scsi.c
@@ -0,0 +1,170 @@
+/* channel_scsi.c -- SCSI device communication channel
+ * Copyright (C) 2008, 2009, 2013 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+/*! \file
+ \brief Implements a SCSI communication channel.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define USE_PROTECTED_CHANNEL_API
+#include "channel.h"
+
+#include <string.h>
+
+#include "utils.h"
+
+/* Deprecated includes */
+#include "epkowa_scsi.h"
+
+
+static void channel_scsi_open (channel *, SANE_Status *);
+static void channel_scsi_close (channel *, SANE_Status *);
+
+static ssize_t channel_scsi_send (channel *, const void *,
+ size_t, SANE_Status *);
+static ssize_t channel_scsi_recv (channel *, void *,
+ size_t, SANE_Status *);
+
+static void channel_scsi_set_max_request_size (channel *, size_t);
+
+
+channel *
+channel_scsi_ctor (channel *self, const char *dev_name, SANE_Status *status)
+{
+ size_t name_len = 0;
+
+ require (self && dev_name);
+ require (0 == strncmp_c (dev_name, "scsi:", strlen ("scsi:")));
+
+ dev_name += strlen ("scsi:");
+ name_len = strlen (dev_name) + 1;
+
+ self->name = t_malloc (name_len, char);
+ if (!self->name)
+ {
+ if (status) *status = SANE_STATUS_NO_MEM;
+ return self->dtor (self);
+ }
+ strcpy (self->name, dev_name);
+
+ self->open = channel_scsi_open;
+ self->close = channel_scsi_close;
+
+ self->send = channel_scsi_send;
+ self->recv = channel_scsi_recv;
+
+ self->set_max_request_size = channel_scsi_set_max_request_size;
+
+ self->max_size = sanei_scsi_max_request_size;
+
+ return self;
+}
+
+static ssize_t
+channel_scsi_send (channel *self, const void *buffer,
+ size_t size, SANE_Status *status)
+{
+ return sanei_epson_scsi_write (self->fd, buffer, size, status);
+}
+
+static ssize_t
+channel_scsi_recv (channel *self, void *buffer,
+ size_t size, SANE_Status *status)
+{
+ return sanei_epson_scsi_read (self->fd, buffer, size, status);
+}
+
+static void
+channel_scsi_open (channel *self, SANE_Status *status)
+{
+ SANE_Status s = sanei_scsi_open (self->name, &self->fd,
+ sanei_epson_scsi_sense_handler, NULL);
+ if (SANE_STATUS_GOOD != s)
+ {
+ err_fatal ("can not open %s (%s)", self->name, sane_strstatus (s));
+ }
+ if (status) *status = s;
+}
+
+static void
+channel_scsi_close (channel *self, SANE_Status *status)
+{
+ sanei_scsi_close (self->fd);
+ self->fd = -1;
+
+ if (status) *status = SANE_STATUS_GOOD;
+}
+
+static void
+channel_scsi_set_max_request_size (channel *self, size_t size)
+{
+ require (self);
+
+ self->max_size = (size < sanei_scsi_max_request_size)
+ ? size : sanei_scsi_max_request_size;
+}
diff --git a/backend/channel-usb.c b/backend/channel-usb.c
new file mode 100644
index 0000000..f83dcb1
--- /dev/null
+++ b/backend/channel-usb.c
@@ -0,0 +1,285 @@
+/* channel_usb.c -- USB device communication channel
+ * Copyright (C) 2008, 2009, 2013 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+/*! \file
+ \brief Implements a USB communication channel.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define USE_PROTECTED_CHANNEL_API
+#include "channel.h"
+
+#include <string.h>
+
+#include "utils.h"
+
+/* Deprecated includes */
+#include "include/sane/sanei_usb.h"
+#include "epkowa_ip.h"
+
+
+static void channel_usb_open (channel *, SANE_Status *);
+static void channel_usb_close (channel *, SANE_Status *);
+
+static ssize_t channel_usb_send (channel *, const void *,
+ size_t, SANE_Status *);
+static ssize_t channel_usb_recv (channel *, void *,
+ size_t, SANE_Status *);
+
+
+channel *
+channel_usb_ctor (channel *self, const char *dev_name, SANE_Status *status)
+{
+ size_t name_len = 0;
+
+ require (self && dev_name);
+ require (0 == strncmp_c (dev_name, "usb:", strlen ("usb:")));
+
+ dev_name += strlen ("usb:");
+ name_len = strlen ("libusb:") + strlen (dev_name) + 1;
+
+ self->name = t_malloc (name_len, char);
+ if (!self->name)
+ {
+ if (status) *status = SANE_STATUS_NO_MEM;
+ return self->dtor (self);
+ }
+ self->name[0] = '\0';
+ strcat (self->name, "libusb:");
+ strcat (self->name, dev_name);
+
+ self->open = channel_usb_open;
+ self->close = channel_usb_close;
+
+ self->send = channel_usb_send;
+ self->recv = channel_usb_recv;
+
+ self->max_size = 128 * 1024;
+
+ return self;
+}
+
+static ssize_t
+channel_usb_send (channel *self, const void *buffer,
+ size_t size, SANE_Status *status)
+{
+ ssize_t n = size;
+
+ if (self->interpreter)
+ {
+ n = self->interpreter->send (self, buffer, size, status);
+ }
+ else
+ {
+ SANE_Status s;
+ s = sanei_usb_write_bulk (self->fd, buffer, (size_t *)&n);
+ if (status) *status = s;
+ }
+
+ return n;
+}
+
+static ssize_t
+channel_usb_recv (channel *self, void *buffer,
+ size_t size, SANE_Status *status)
+{
+ ssize_t n = size;
+
+ if (self->interpreter)
+ {
+ n = self->interpreter->recv (self, buffer, size, status);
+ }
+ else
+ {
+ SANE_Status s = SANE_STATUS_GOOD;
+ s = sanei_usb_read_bulk (self->fd, (SANE_Byte *) buffer, (size_t *)&n);
+ if (status) *status = s;
+ }
+
+ return n;
+}
+
+static void
+channel_usb_open (channel *self, SANE_Status *status)
+{
+ SANE_Status s;
+
+ s = sanei_usb_open (self->name, &self->fd);
+
+ if (SANE_STATUS_GOOD == s)
+ {
+ SANE_Word product_id = -1;
+
+ sanei_usb_get_vendor_product (self->fd, NULL, &product_id);
+
+ if (-1 != product_id)
+ {
+ self->id = product_id;
+ }
+ }
+
+ if (self->interpreter && SANE_STATUS_GOOD == s)
+ {
+ if (0 > self->interpreter->open (self))
+ {
+ s = SANE_STATUS_IO_ERROR;
+ }
+ }
+
+ if (status) *status = s;
+}
+
+static void
+channel_usb_close (channel *self, SANE_Status *status)
+{
+ SANE_Status s = SANE_STATUS_GOOD;
+
+ if (self->interpreter)
+ {
+ self->interpreter->close (self);
+ }
+
+ sanei_usb_close (self->fd);
+ self->fd = -1;
+ if (status) *status = s;
+}
+
+
+static channel * channel_interpreter_dtor (channel *self);
+
+channel *
+channel_interpreter_ctor (channel *self, const char *dev_name,
+ SANE_Status *status)
+{
+ char *name = NULL;
+ size_t name_len = 0;
+
+ require (self && dev_name);
+ require (0 == strncmp_c (dev_name, "interpreter:", strlen ("interpreter:")));
+
+ dev_name += strlen ("interpreter:");
+ name_len = strlen ("usb:") + strlen (dev_name) + 1;
+
+ name = t_malloc (name_len, char);
+ if (!name)
+ {
+ if (status) *status = SANE_STATUS_NO_MEM;
+ return self->dtor (self);
+ }
+ strcpy (name, "usb:");
+ strcat (name, dev_name);
+
+ self = channel_usb_ctor (self, name, status);
+ delete (name);
+
+ if (self)
+ {
+ SANE_Status s = SANE_STATUS_GOOD;
+ SANE_Word vendor;
+ SANE_Word product;
+
+ self->open (self, &s);
+ if (SANE_STATUS_GOOD == s)
+ {
+ s = sanei_usb_get_vendor_product (self->fd,
+ &vendor, &product);
+ }
+ self->close (self, NULL);
+ if (SANE_STATUS_GOOD == s)
+ {
+ s = create_interpreter (self, product);
+ }
+
+ if (!self->interpreter)
+ {
+ if (status) *status = s;
+ return self->dtor (self);
+ }
+ else
+ {
+ self->dtor = channel_interpreter_dtor;
+ }
+ }
+
+ self->max_size = 32 * 1024;
+
+ return self;
+}
+
+static channel *
+channel_interpreter_dtor (channel *self)
+{
+ require (self);
+
+ if (self->interpreter)
+ {
+ self->interpreter->dtor (self);
+ }
+ self->dtor = channel_dtor;
+ return self->dtor (self);
+}
diff --git a/backend/channel.c b/backend/channel.c
new file mode 100644
index 0000000..754f640
--- /dev/null
+++ b/backend/channel.c
@@ -0,0 +1,321 @@
+/* channel.c -- device communication channel
+ * Copyright (C) 2008, 2009, 2013 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+/*! \file
+ \brief Implements a hardware communication channel.
+
+ Hardware channels supported are usb, scsi and parallel.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define USE_PROTECTED_CHANNEL_API
+#include "channel.h"
+
+#include "utils.h"
+
+extern channel *
+channel_net_ctor (channel *self, const char *dev_name, SANE_Status *status);
+
+extern channel *
+channel_pio_ctor (channel *self, const char *dev_name, SANE_Status *status);
+
+extern channel *
+channel_scsi_ctor (channel *self, const char *dev_name, SANE_Status *status);
+
+extern channel *
+channel_usb_ctor (channel *self, const char *dev_name, SANE_Status *status);
+
+extern channel *
+channel_interpreter_ctor (channel *self,
+ const char *dev_name, SANE_Status *status);
+
+
+#include <string.h>
+
+#include "epkowa_ip.h"
+
+#include "hw-data.h"
+
+
+/*! A ::channel factory method.
+ *
+ * Creates and initializes a channel object to match a \a dev_name.
+ *
+ * \todo Validate \a dev_name?
+ */
+channel *
+channel_create (const char *dev_name, SANE_Status *status)
+{
+ channel *ch = NULL;
+
+ require (dev_name);
+
+ if (status) *status = SANE_STATUS_GOOD;
+
+ ch = t_calloc (1, channel);
+ if (!ch)
+ {
+ if (status) *status = SANE_STATUS_NO_MEM;
+ return NULL;
+ }
+
+ ch->dtor = channel_dtor;
+ ch->is_open = channel_is_open;
+ ch->max_request_size = channel_max_request_size;
+ ch->set_max_request_size = channel_set_max_request_size;
+
+ ch->fd = -1;
+ ch->id = 0;
+ ch->max_size = 32 * 1024;
+
+ if (0 == strncmp_c (dev_name, "net:", strlen ("net:")))
+ {
+ ch->ctor = channel_net_ctor;
+ ch->type = CHAN_NET;
+ }
+ if (0 == strncmp_c (dev_name, "pio:", strlen ("pio:")))
+ {
+ ch->ctor = channel_pio_ctor;
+ ch->type = CHAN_PIO;
+ }
+ if (0 == strncmp_c (dev_name, "scsi:", strlen ("scsi:")))
+ {
+ ch->ctor = channel_scsi_ctor;
+ ch->type = CHAN_SCSI;
+ }
+ if (0 == strncmp_c (dev_name, "usb:", strlen ("usb:")))
+ {
+ ch->ctor = channel_usb_ctor;
+ ch->type = CHAN_USB;
+ }
+ if (0 == strncmp_c (dev_name, "interpreter:", strlen ("interpreter:")))
+ {
+ ch->ctor = channel_interpreter_ctor;
+ ch->type = CHAN_INTERP;
+ }
+
+ if (!ch->ctor)
+ {
+ err_major ("unsupported channel for '%s'", dev_name);
+ if (status) *status = SANE_STATUS_UNSUPPORTED;
+ delete (ch);
+ return NULL;
+ }
+
+ return ch->ctor (ch, dev_name, status);
+}
+
+/*! Logging wrapper around a channel's send() method.
+ */
+ssize_t
+channel_send (channel* ch, const void *buffer, size_t size,
+ SANE_Status *status)
+{
+ ssize_t n = 0;
+
+ log_call ("(%zd)", size);
+ dbg_hex (buffer, size);
+
+ n = ch->send (ch, buffer, size, status);
+
+ log_call ("transferred %zd bytes", n);
+ return n;
+}
+
+/*! Logging wrapper around a channel's recv() method.
+ */
+ssize_t
+channel_recv (channel *ch, void *buffer, size_t size, SANE_Status *status)
+{
+ ssize_t n = 0;
+
+ log_call ("(%zd)", size);
+
+ if (size < 256)
+ memset (buffer, 0x00, size);
+
+ n = ch->recv (ch, buffer, size, status);
+
+ if (0 < n)
+ {
+ if (size < 256)
+ { dbg_hex (buffer, n); }
+ else
+ { dbg_img (buffer, n); }
+ }
+
+ log_call ("transferred %zd bytes", n);
+ return n;
+}
+
+/*! Throttle the number of bytes read in a single go
+ */
+static ssize_t
+channel_recv_throttle (channel *ch, void *buffer, size_t size,
+ SANE_Status *status)
+{
+ size_t max = ch->max_request_size (ch);
+ return ch->recv (ch, buffer, size < max ? size : max, status);
+}
+
+ssize_t channel_recv_all (channel *ch, void *buffer,
+ size_t size, SANE_Status *status)
+{
+ return channel_recv_all_retry (ch, buffer, size, 1, status);
+}
+
+ssize_t
+channel_recv_all_retry (channel *ch, void *buffer, size_t size,
+ size_t max_attempts, SANE_Status *status)
+{
+ SANE_Status s = SANE_STATUS_GOOD;
+
+ ssize_t n = 0;
+ ssize_t t = 0;
+ size_t attempts = 0;
+
+ log_call ("(%zd)", size);
+
+ while (n < size && attempts < max_attempts)
+ {
+ t = channel_recv_throttle (ch, buffer + n, size - n, &s);
+ if (SANE_STATUS_GOOD != s || 0 >= t)
+ {
+ ++attempts;
+ log_info ("attempts: %zd/%zd", attempts, max_attempts);
+ }
+ if (0 < t) n += t;
+ log_call ("transferred %zd bytes, total %zd/%zd", t, n, size);
+ }
+
+ if (0 < n)
+ {
+ if (size < 256)
+ { dbg_hex (buffer, n); }
+ else
+ { dbg_img (buffer, n); }
+ }
+
+ if (status) *status = s;
+
+ return n;
+}
+
+/*! Tells whether a channel is ready to send() and recv() data.
+ */
+bool
+channel_is_open (const struct channel *self)
+{
+ return (self && 0 <= self->fd);
+}
+
+/*! Indicates the maximum number of bytes the channel should read
+ * in a singe request.
+ */
+size_t
+channel_max_request_size (const struct channel *self)
+{
+ require (self);
+
+ return self->max_size;
+}
+
+/*! Change the maximum number of bytes a channel should read in a
+ * single request.
+ */
+void
+channel_set_max_request_size (struct channel *self, size_t size)
+{
+ require (self);
+
+ self->max_size = size;
+}
+
+/*! "Base class" destructor.
+ */
+channel *
+channel_dtor (struct channel *self)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ log_call ("(fd = %d)", self->fd);
+
+ if (!self) return NULL;
+
+ if (self->interpreter) self->interpreter->dtor (self);
+
+ if (self->is_open (self))
+ {
+ self->close (self, &status);
+ }
+
+ delete (self->name);
+ delete (self);
+
+ return NULL;
+}
diff --git a/backend/channel.h b/backend/channel.h
new file mode 100644
index 0000000..b89732d
--- /dev/null
+++ b/backend/channel.h
@@ -0,0 +1,143 @@
+/* channel.h -- device communication channels
+ * Copyright (C) 2008, 2009, 2013 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifndef channel_h_included
+#define channel_h_included
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sane/sane.h>
+#include <sys/types.h>
+#include <stdint.h>
+
+#include "defines.h"
+
+typedef enum
+{
+ CHAN_NET = 0,
+ CHAN_PIO,
+ CHAN_SCSI,
+ CHAN_USB,
+ CHAN_INTERP
+}
+channel_type;
+
+typedef struct channel
+{
+ struct channel * (*ctor) (struct channel *self,
+ const char *dev_name, SANE_Status *status);
+ struct channel * (*dtor) (struct channel *self);
+
+ void (*open) (struct channel *self, SANE_Status *status);
+ void (*close) (struct channel *self, SANE_Status *status);
+
+ bool (*is_open) (const struct channel *self);
+
+ ssize_t (*send) (struct channel *self, const void *buffer,
+ size_t buf_size, SANE_Status *status);
+ ssize_t (*recv) (struct channel *self, void *buffer,
+ size_t buf_size, SANE_Status *status);
+ size_t (*max_request_size) (const struct channel *self);
+ void (*set_max_request_size) (struct channel *self, size_t size);
+
+ char *name;
+ channel_type type;
+ int fd;
+ uint16_t id; /* target scanner ID when used with the network plugin
+ * or USB product ID
+ */
+ size_t max_size;
+
+ struct interpreter_type *interpreter;
+
+} channel;
+
+
+channel * channel_create (const char *dev_name, SANE_Status *status);
+
+
+/* Convenience API */
+ssize_t channel_send (channel *ch, const void *buffer,
+ size_t size, SANE_Status *status);
+ssize_t channel_recv (channel *ch, void *buffer,
+ size_t size, SANE_Status *status);
+ssize_t channel_recv_all (channel *ch, void *buffer,
+ size_t size, SANE_Status *status);
+ssize_t channel_recv_all_retry (channel *ch, void *buffer, size_t size,
+ size_t max_attempts, SANE_Status *status);
+
+
+#ifdef USE_PROTECTED_CHANNEL_API
+
+channel *channel_dtor (channel *self);
+bool channel_is_open (const channel *self);
+size_t channel_max_request_size (const channel *self);
+void channel_set_max_request_size (struct channel *self, size_t size);
+
+#endif
+
+
+#endif /* !defined (channel_h_included) */
diff --git a/backend/command.c b/backend/command.c
new file mode 100644
index 0000000..7c35aa6
--- /dev/null
+++ b/backend/command.c
@@ -0,0 +1,814 @@
+/* command.c -- assorted ESC/I protocol commands
+ * Copyright (C) 2008--2014 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+/*! \file
+ \brief Implements 'bare' ESC/I commands.
+
+ This file contains functions that implement of a number of ESC/I
+ commands. These functions only handle the sending and receiving
+ of data, data interpretation and I/O or memory related errors.
+
+ Interpretation of received data is limited to taking bytes apart
+ into bit flags and combining bytes into larger entities, such as
+ 2- or 4-byte integers and strings. The hardware device object's
+ state is updated to correspond with the interpreted data.
+
+ Under \e no circumstances shall the functions implemented in this
+ file draw any conclusions whatsoever as to the device's resulting
+ state. This is the reponsibility of the hardware device object.
+ Also, it is the hardware device object's responsibility to check
+ the timing/validity of calling any of the functions implemented
+ here.
+
+ All functions shall return a status that is one of:
+
+ - \c SANE_STATUS_GOOD
+ - \c SANE_STATUS_NO_MEM
+ - \c SANE_STATUS_IO_ERROR
+ - \c SANE_STATUS_INVAL
+
+ Function names follow the description in the ESC/I specification
+ and shall start with a \c cmd_ prefix.
+
+ \note I want to replace the device::cmd crap with function
+ pointers and use a no-op for commands that are not supported by
+ the device. Functions may return SANE_STATUS_UNSUPPORTED in the
+ interim.
+
+ \note I also want to introduce a function pointer based callback
+ mechanism to fix up broken firmware replies. That way, we do not
+ have to pass the firmware name everytime. We can set appropriate
+ callbacks for a device once and be done with it.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "command.h"
+
+#include "utils.h"
+
+#include <string.h>
+
+#define SANE_INT_MAX 2147483647
+
+
+static void
+fixme_request_identity (const char *fw_name, byte *buf, size_t size)
+{
+ if (!fw_name) return;
+ if (!buf) return;
+
+ if (0 == strcmp_c ("NX100", fw_name) && 16 < size)
+ {
+ buf[12] = 'A';
+ buf[13] = 0xEC;
+ buf[14] = 0x13;
+ buf[15] = 0x6C;
+ buf[16] = 0x1B;
+ }
+}
+
+/*! \brief Establishes basic device capabilities.
+
+ \note This command is assumed to be supported by \e all ESC/I
+ devices.
+
+ \todo Implement error checking.
+ */
+SANE_Status
+cmd_request_identity (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { ESC, 'I' };
+ byte info[4];
+
+ byte *data = NULL;
+ size_t size;
+
+ log_call ();
+ require (hw);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ channel_recv (hw->channel, info, num_of (info), &status);
+
+ hw->status = info[1];
+ size = info[3] << 8 | info[2];
+
+ if (0 < size)
+ {
+ data = t_calloc (size, byte);
+ if (!data)
+ {
+ return SANE_STATUS_NO_MEM;
+ }
+
+ channel_recv (hw->channel, data, size, &status);
+ if (SANE_STATUS_GOOD == status)
+ {
+ byte *p = data + 2;
+
+ fixme_request_identity (hw->fw_name, data, size);
+
+ hw->cmd_lvl[0] = data[0];
+ hw->cmd_lvl[1] = data[1];
+
+ free_resolution_info (&hw->res);
+ init_resolution_info (&hw->res, p);
+ init_resolution_info (&hw->resolution, NULL);
+ copy_resolution_info (&hw->resolution, &hw->res, SANE_TRUE);
+
+ hw->max_x = data[size-3] << 8 | data[size-4];
+ hw->max_y = data[size-1] << 8 | data[size-2];
+ }
+ delete (data);
+ }
+
+ return status;
+}
+
+static void
+fixme_request_hardware_property (const char *fw_name, byte *buf, size_t size)
+{
+ if (!fw_name) return;
+ if (!buf) return;
+
+ if (0 == strcmp_c ("NX100", fw_name) && 33 < size)
+ {
+ buf[32] = 0xB0;
+ buf[33] = 0x04;
+ }
+}
+
+/*! \brief Query additional device capabilities.
+
+ \note This command is not supported for B level devices. It is
+ supported for D level devices.
+
+ \todo Implement error checking.
+ */
+SANE_Status
+cmd_request_hardware_property (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { ESC, 'i' };
+ byte info[4];
+
+ byte *data = NULL;
+ size_t size;
+
+ log_call ();
+ require (hw);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ channel_recv (hw->channel, info, num_of (info), &status);
+
+ hw->status = info[1];
+ size = info[3] << 8 | info[2];
+
+ if (0 < size)
+ {
+ data = t_calloc (size, byte);
+ if (!data)
+ {
+ return SANE_STATUS_NO_MEM;
+ }
+
+ channel_recv (hw->channel, data, size, &status);
+ if (SANE_STATUS_GOOD == status)
+ {
+ byte *p = data + 14;
+
+ fixme_request_hardware_property (hw->fw_name, data, size);
+
+ hw->optical_res = data[1] << 8 | data[0];
+ hw->sensor_info = data[2];
+ hw->scan_order = data[3];
+ hw->line_dist_x = data[4];
+ hw->line_dist_y = data[5];
+
+ free_resolution_info (&hw->res_x);
+ init_resolution_info (&hw->res_x, p);
+
+ while (resolution_info_ESC_i_cond (p))
+ p += 2;
+
+ p += 2; /* start of sub resolution info */
+ free_resolution_info (&hw->res_y);
+ init_resolution_info (&hw->res_y, p);
+ }
+ delete (data);
+ }
+
+ return status;
+}
+
+SANE_Status
+cmd_set_scanning_parameter (device* hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte FS_W[] = {FS, 'W'};
+
+ byte ack_buf;
+
+ log_call ();
+ require (hw);
+
+ channel_send (hw->channel, FS_W, 2, &status);
+ if (SANE_STATUS_GOOD != status) return status;
+ channel_recv (hw->channel, &ack_buf, 1, &status);
+ if (SANE_STATUS_GOOD != status) return status;
+ if (ACK != ack_buf) return SANE_STATUS_UNSUPPORTED;
+
+ channel_send (hw->channel, hw->param_buf, 64, &status);
+ if (SANE_STATUS_GOOD != status) return status;
+ channel_recv (hw->channel, &ack_buf, 1, &status);
+ if (SANE_STATUS_GOOD != status) return status;
+ if (ACK != ack_buf) return SANE_STATUS_INVAL;
+
+ return status;
+}
+
+SANE_Status
+cmd_request_scanning_parameter (device* hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+ const byte FS_S[] = {FS, 'S'};
+
+ log_call ();
+ require (hw);
+
+ channel_send (hw->channel, FS_S, 2, &status);
+ if (SANE_STATUS_GOOD != status) return status;
+ channel_recv (hw->channel, hw->param_buf, 64, &status);
+
+ return status;
+}
+
+SANE_Status
+cmd_request_scanner_status (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { FS, 'F' };
+ byte buf[16];
+
+ log_call ();
+ require (hw);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ channel_recv (hw->channel, buf, num_of (buf), &status);
+
+ hw->fsf_status = buf[0];
+ { /* transfer corresponding flags */
+ byte mask = FSF_STATUS_FER | FSF_STATUS_WU;
+ hw->ext_status &= ~mask;
+ hw->ext_status |= (mask & hw->fsf_status);
+ }
+
+ if ((ADF_STATUS_IST & buf[1]) && !hw->adf)
+ {
+ hw->adf = t_calloc (1, adf_extension);
+ if (!hw->adf) return SANE_STATUS_NO_MEM;
+ }
+ if ((TPU_STATUS_IST & buf[2]) && !hw->tpu)
+ {
+ hw->tpu = t_calloc (1, tpu_extension);
+ if (!hw->tpu) return SANE_STATUS_NO_MEM;
+ }
+
+ if (hw->fbf)
+ {
+ hw->fbf->status = buf[3];
+ update_doc_size (hw->fbf, buf[8] << 8 | buf[7]);
+ }
+ if (hw->adf)
+ {
+ hw->adf->status = buf[1];
+ hw->adf->ext_status = buf[10];
+ update_doc_size (hw->adf, buf[6] << 8 | buf[5]);
+ }
+ if (hw->tpu)
+ {
+ hw->tpu->status = buf[2];
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+static SANE_Int
+buf_to_sane_int (const byte *p, const char *var)
+{
+ SANE_Int result = 0;
+
+ require (p);
+ result = buf_to_uint32 (p);
+
+ if (SANE_INT_MAX < result)
+ {
+ err_major ("overflow: %s", var);
+ result = SANE_INT_MAX;
+ }
+ return result;
+}
+
+SANE_Status
+cmd_request_extended_identity (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { FS, 'I' };
+ byte buf[80];
+
+ log_call ();
+ require (hw);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ channel_recv (hw->channel, buf, num_of (buf), &status);
+
+ hw->cmd_lvl[0] = buf[0];
+ hw->cmd_lvl[1] = buf[1];
+
+ hw->version[0] = buf[62];
+ hw->version[1] = buf[63];
+ hw->version[2] = buf[64];
+ hw->version[3] = buf[65];
+
+ hw->fsi_cap_1 = buf[44];
+ hw->fsi_cap_2 = buf[45];
+ hw->fsi_cap_3 = buf[76];
+ { /* transfer corresponding flags */
+ byte mask = (EXT_STATUS_NO_FBF | EXT_STATUS_ADFT | EXT_STATUS_ADFS
+ | EXT_STATUS_ADFO | EXT_STATUS_LID | EXT_STATUS_PB);
+ hw->ext_status &= ~mask;
+ hw->ext_status |= (mask & hw->fsi_cap_1);
+ }
+
+ if (!(EXT_STATUS_NO_FBF & hw->fsi_cap_1) && !hw->fbf)
+ {
+ hw->fbf = t_calloc (1, fbf_extension);
+ if (!hw->fbf) return SANE_STATUS_NO_MEM;
+ }
+
+ hw->cmd->request_push_button_status
+ = ((EXT_STATUS_PB & hw->fsi_cap_1) ? '!' : 0);
+
+ hw->base_res = buf_to_sane_int (buf + 4, "base resolution");
+
+ hw->dpi_range.min = buf_to_sane_int (buf + 8, "minimum resolution");
+ hw->dpi_range.max = buf_to_sane_int (buf + 12, "maixmum resolution");
+ hw->dpi_range.quant = 1;
+
+ hw->scan_width_limit = buf_to_sane_int (buf + 16, "scan width");
+
+ if (hw->fbf)
+ {
+ hw->fbf->max_x = buf_to_sane_int (buf + 20, "fbf max_x");
+ hw->fbf->max_y = buf_to_sane_int (buf + 24, "fbf max_y");
+ update_ranges (hw, hw->fbf);
+ }
+ if (hw->adf)
+ {
+ hw->adf->max_x = buf_to_sane_int (buf + 28, "adf max_x");
+ hw->adf->max_y = buf_to_sane_int (buf + 32, "adf max_y");
+ update_ranges (hw, hw->adf);
+ }
+ if (hw->tpu)
+ {
+ hw->tpu->max_x = buf_to_sane_int (buf + 36, "tpu max_x");
+ hw->tpu->max_y = buf_to_sane_int (buf + 40, "tpu max_y");
+ update_ranges (hw, hw->tpu);
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+static void
+fixme_request_extended_status (const char *fw_name, byte *buf, size_t size)
+{
+ if (!fw_name) return;
+ if (!buf) return;
+
+ if (0 == strcmp_c ("GT-8200", fw_name) && 15 < size)
+ {
+ uint16_t max_x;
+ uint16_t max_y;
+
+ max_x = buf[13] << 8 | buf[12];
+ max_y = buf[15] << 8 | buf[14];
+ if (max_y < max_x)
+ {
+ err_minor ("Fixing up buggy FBF max scan dimensions.");
+ max_y *= 2;
+ buf[14] = 0xFF & max_y;
+ buf[15] = 0xFF & (max_y >> 8);
+ }
+
+ max_x = buf[ 8] << 8 | buf[7];
+ max_y = buf[10] << 8 | buf[9];
+ if (max_y < max_x)
+ {
+ err_minor ("Fixing up buggy TPU max scan dimensions.");
+ max_y *= 2;
+ buf[ 9] = 0xFF & max_y;
+ buf[10] = 0xFF & (max_y >> 8);
+ }
+ }
+
+ if ((0 == strcmp_c ("ES-9000H", fw_name) ||
+ 0 == strcmp_c ("GT-30000", fw_name))
+ && 5 < size)
+ {
+ err_minor ("Fixing up buggy ADF max scan dimensions.");
+ buf[2] = 0xB0;
+ buf[3] = 0x6D;
+ buf[4] = 0x60;
+ buf[5] = 0x9F;
+ }
+}
+
+/*! Updates the extended status of a hardware device object.
+ */
+SANE_Status
+cmd_request_extended_status (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { ESC, 'f' };
+ byte info[4];
+
+ byte *data = NULL;
+ size_t size;
+
+ const size_t DEVNAME_OFFSET = 26;
+
+ const byte DEVT_MASK = 0xC0;
+ const byte DEVTYPE_3 = 0xC0;
+
+ log_call ();
+ require (hw);
+
+ if (!hw->cmd->request_extended_status)
+ return SANE_STATUS_UNSUPPORTED;
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ channel_recv (hw->channel, info, num_of (info), &status);
+
+ hw->status = info[1];
+ size = info[3] << 8 | info[2];
+
+ require (DEVNAME_OFFSET + DEVNAME_LENGTH <= size);
+
+ if (0 < size)
+ {
+ data = t_calloc (size, byte);
+ if (!data)
+ {
+ return SANE_STATUS_NO_MEM;
+ }
+
+ channel_recv (hw->channel, data, size, &status);
+ if (SANE_STATUS_GOOD == status)
+ {
+ fixme_request_extended_status (hw->fw_name, data, size);
+
+ hw->ext_status = data[0];
+
+ hw->cmd->request_push_button_status
+ = ((EXT_STATUS_PB & data[0]) ? '!' : 0);
+
+ if (!(EXT_STATUS_NO_FBF & data[0]) && !hw->fbf)
+ {
+ hw->fbf = t_calloc (1, fbf_extension);
+ if (!hw->fbf) status = SANE_STATUS_NO_MEM;
+ }
+ if ((ADF_STATUS_IST & data[1]) && !hw->adf)
+ {
+ hw->adf = t_calloc (1, adf_extension);
+ if (!hw->adf) status = SANE_STATUS_NO_MEM;
+ }
+ if ((TPU_STATUS_IST & data[6]) && !hw->tpu)
+ {
+ hw->tpu = t_calloc (1, tpu_extension);
+ if (!hw->tpu) status = SANE_STATUS_NO_MEM;
+ }
+
+ if (hw->fbf)
+ {
+ hw->fbf->status = 0x00;
+ if (DEVTYPE_3 == (DEVT_MASK & data[11]))
+ {
+ hw->fbf->status = data[11];
+ hw->fbf->max_x = data[13] << 8 | data[12];
+ hw->fbf->max_y = data[15] << 8 | data[14];
+ }
+ else
+ {
+ hw->fbf->max_x = hw->max_x;
+ hw->fbf->max_y = hw->max_y;
+ }
+ update_ranges (hw, hw->fbf);
+ update_doc_size (hw->fbf, data[19] << 8 | data[18]);
+ }
+ if (hw->adf)
+ {
+ hw->adf->status = data[1];
+ hw->adf->max_x = data[3] << 8 | data[2];
+ hw->adf->max_y = data[5] << 8 | data[4];
+ update_ranges (hw, hw->adf);
+ update_doc_size (hw->adf, data[17] << 8 | data[16]);
+ }
+ if (hw->tpu)
+ {
+ hw->tpu->status = data[6];
+ hw->tpu->max_x = data[ 8] << 8 | data[7];
+ hw->tpu->max_y = data[10] << 8 | data[9];
+ update_ranges (hw, hw->tpu);
+ }
+ }
+ delete (data);
+ }
+
+ return status;
+}
+
+
+/*! Sets the option unit to use as well as the unit's behaviour.
+
+ \todo Implement error checking.
+ */
+SANE_Status
+cmd_control_option_unit (device *hw, byte value)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { ESC, 'e' };
+ byte reply = NUL;
+
+ log_call ();
+ require (hw);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ channel_recv (hw->channel, &reply, 1, &status);
+ channel_send (hw->channel, &value, 1, &status);
+ channel_recv (hw->channel, &reply, 1, &status);
+
+ return status;
+}
+
+
+/*! Resets the device to a well known state.
+
+ \todo Implement error checking.
+ */
+SANE_Status
+cmd_initialize (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { ESC, '@' };
+ byte reply = NUL;
+
+ log_call ();
+ require (hw);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ channel_recv (hw->channel, &reply, 1, &status);
+
+ return status;
+}
+
+/*! Loads a sheet on a page type ADF extension.
+
+ \todo Implement error checking.
+ */
+SANE_Status
+cmd_load_paper (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { PF };
+ byte reply = NUL;
+
+ log_call ();
+ require (hw);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ channel_recv (hw->channel, &reply, 1, &status);
+
+ return status;
+}
+
+/*! Ejects sheets from the ADF extension.
+
+ \todo Implement error checking.
+ */
+SANE_Status
+cmd_eject_paper (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { FF };
+ byte reply = NUL;
+
+ log_call ();
+ require (hw);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ channel_recv (hw->channel, &reply, 1, &status);
+
+ return status;
+}
+
+SANE_Status
+cmd_lock (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { ESC, '(' };
+ byte reply = NUL;
+
+ log_call();
+ require (hw);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ if (SANE_STATUS_GOOD != status) return status;
+ channel_recv (hw->channel, &reply, 1, &status);
+
+ if (SANE_STATUS_GOOD == status)
+ {
+ if (0x80 == reply)
+ {
+ hw->is_locked = true;
+ }
+ else if (0x40 == reply)
+ {
+ err_minor ("failed to acquire lock");
+ status = SANE_STATUS_DEVICE_BUSY;
+ }
+ else if (NAK == reply)
+ {
+ err_minor ("locking not supported by device, disabling");
+ hw->uses_locking = false;
+ status = SANE_STATUS_GOOD;
+ }
+ else
+ {
+ err_major ("unexpected reply to lock command (%02x)", reply);
+ status = SANE_STATUS_IO_ERROR;
+ }
+ }
+ return status;
+}
+
+SANE_Status
+cmd_unlock (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { ESC, ')' };
+ byte reply = NUL;
+
+ log_call();
+ require (hw);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ if (SANE_STATUS_GOOD != status) return status;
+ channel_recv (hw->channel, &reply, 1, &status);
+
+ if (SANE_STATUS_GOOD == status)
+ {
+ if (0x80 == reply)
+ {
+ hw->is_locked = false;
+ }
+ else if (NAK == reply)
+ {
+ err_minor ("locking not supported by device, disabling");
+ hw->uses_locking = false;
+ status = SANE_STATUS_GOOD;
+ }
+ else
+ {
+ err_major ("unexpected reply to unlock command (%02x)", reply);
+ status = SANE_STATUS_IO_ERROR;
+ }
+ }
+
+ return status;
+}
+
+SANE_Status
+cmd_request_scanner_maintenance (device *hw, uint16_t mode)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { ESC, '1' };
+ byte param[8];
+ byte reply = NUL;
+
+ log_call ("(%04x)", mode);
+ require (hw);
+
+ memset (param, 0, sizeof (param));
+ uint16_to_buf (mode, param);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ if (SANE_STATUS_GOOD != status) return status;
+ channel_recv (hw->channel, &reply, sizeof (reply), &status);
+ if (SANE_STATUS_GOOD != status) return status;
+
+ if (ACK != reply)
+ {
+ err_major ("unexpected reply to maintenance command (%02x)", reply);
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ channel_send (hw->channel, (const byte *)param, num_of (param), &status);
+ if (SANE_STATUS_GOOD != status) return status;
+ channel_recv (hw->channel, &reply, sizeof (reply), &status);
+ if (SANE_STATUS_GOOD != status) return status;
+
+ if (BUSY == reply)
+ {
+ status = SANE_STATUS_DEVICE_BUSY;
+ }
+ else if (NAK == reply)
+ {
+ err_minor ("invalid maintenance command (%04x)", mode);
+ status = SANE_STATUS_INVAL;
+ }
+ else if (ACK != reply)
+ {
+ err_major ("unexpected reply to maintenance command (mode=%04x, %02x)",
+ mode, reply);
+ status = SANE_STATUS_IO_ERROR;
+ }
+ return status;
+}
diff --git a/backend/command.h b/backend/command.h
new file mode 100644
index 0000000..774984c
--- /dev/null
+++ b/backend/command.h
@@ -0,0 +1,223 @@
+/* command.h -- assorted ESC/I protocol commands
+ * Copyright (C) 2008--2014 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifndef command_h_included
+#define command_h_included
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+
+/* ESC/I protocol constants.
+ */
+
+/* Readability definitions for selected ASCII codes used by the ESC/I
+ protocol.
+ */
+#define NUL 0x00 /* '\0' */
+#define STX 0x02
+#define ACK 0x06
+#define BUSY 0x07 /* BEL in ascii(7) */
+#define FF 0x0c /* '\f' */
+#define NAK 0x15
+#define CAN 0x18
+#define PF 0x19 /* EM in ascii(7) */
+#define ESC 0x1b
+#define FS 0x1c
+
+
+/* Bit flags for the status byte in the information block.
+ */
+#define STATUS_FATAL_ERROR 0x80
+#define STATUS_NOT_READY 0x40 /* in use via other interface */
+#define STATUS_AREA_END 0x20 /* scan finished */
+#define STATUS_OPTION 0x10 /* option detected */
+#define STATUS_COLOR_ATTR_MASK 0x0c
+#define STATUS_EXT_COMMANDS 0x02 /* FS commands supported */
+
+/* Bit flags for the extended image acquisition (FS G) error byte
+ */
+#define FSG_FATAL_ERROR 0x80
+#define FSG_NOT_READY 0x40 /* in use via other interface */
+#define FSG_PAGE_END 0x20 /* notify of the paper end */
+#define FSG_CANCEL_REQUEST 0x10 /* cancel request from scanner */
+
+/* Bit flags for selected bytes in the extended status and scanner
+ status replies. Synchronised status flags are defined in terms
+ of the corresponding flag. All other values are supposed to be
+ unlinked. The various *_ERR flags indicate that at least one of
+ the other error bit flags in the _same_ block is set.
+
+ The EXT_STATUS_* flags are specific to the ESC f command's reply,
+ The FSF_STATUS_*, ADF_EXT_STATUS_* and AFL_STATUS_* flags to that
+ of the FS F command.
+ */
+#define EXT_STATUS_FER 0x80 /* fatal error */
+#define EXT_STATUS_NO_FBF 0x40 /* no flat bed */
+#define EXT_STATUS_ADFT 0x20 /* ADF unit type */
+#define EXT_STATUS_ADFS 0x10 /* simplex/duplex */
+#define EXT_STATUS_ADFO 0x08 /* feed from first/last sheet */
+#define EXT_STATUS_LID 0x04 /* lid type option */
+#define EXT_STATUS_WU 0x02 /* warming up */
+#define EXT_STATUS_PB 0x01 /* scanner has a push button */
+
+#define FSF_STATUS_FER 0x80 /* fatal error */
+#define FSF_STATUS_NR 0x40 /* in use via other interface */
+#define FSF_STATUS_WU 0x02 /* warming up */
+#define FSF_STATUS_CWU 0x01 /* can cancel warming up */
+
+#define ADF_STATUS_IST 0x80 /* option detected */
+#define ADF_STATUS_EN 0x40 /* option enabled */
+#define ADF_STATUS_ERR 0x20 /* option error detected */
+#define ADF_STATUS_ATYP 0x10 /* photo ADF type detected */
+#define ADF_STATUS_PE 0x08 /* no paper */
+#define ADF_STATUS_PJ 0x04 /* paper jam */
+#define ADF_STATUS_OPN 0x02 /* cover open */
+#define ADF_STATUS_PAG 0x01 /* duplex selected */
+
+#define ADF_EXT_STATUS_IST ADF_STATUS_IST
+#define ADF_EXT_STATUS_EN ADF_STATUS_EN
+#define ADF_EXT_STATUS_ERR 0x20 /* option error detected */
+#define ADF_EXT_STATUS_DFE 0x10 /* double feed */
+#define ADF_EXT_STATUS_TR_OPN 0x02 /* tray open */
+
+#define TPU_STATUS_IST 0x80 /* option detected */
+#define TPU_STATUS_EN 0x40 /* option enabled */
+#define TPU_STATUS_ERR 0x20 /* option error detected */
+#define TPU_STATUS_OPN 0x02 /* cover open */
+#define TPU_STATUS_LTF 0x01 /* lamp luminescence too low */
+
+#define AFL_STATUS_IST 0x80 /* option detected */
+#define AFL_STATUS_EN 0x40 /* option enabled */
+#define AFL_STATUS_ERR 0x20 /* option error detected */
+#define AFL_STATUS_OPN 0x02 /* cover open */
+#define AFL_STATUS_LTF 0x01 /* lamp luminescence too low */
+
+#define DV3_STATUS_ERR 0x20
+#define DV3_STATUS_PE 0x08 /* no paper */
+#define DV3_STATUS_PJ 0x04 /* paper jam */
+#define DV3_STATUS_OPN 0x02 /* cover open */
+
+/* Bit flags
+ */
+#define FSI_CAP_DLF 0x80 /* ??? */
+#define FSI_CAP_NO_FBF 0x40 /* no flat bed */
+#define FSI_CAP_ADFT 0x20 /* ADF unit type */
+#define FSI_CAP_ADFS 0x10 /* simplex/duplex */
+#define FSI_CAP_ADFO 0x08 /* feed from first/last sheet */
+#define FSI_CAP_LID 0x04 /* lid type option */
+#define FSI_CAP_TPIR 0x02 /* TPU with IR support */
+#define FSI_CAP_PB 0x01 /* scanner has a push button */
+
+#define FSI_CAP_ADFAS 0x10 /* ADF with auto scan support */
+#define FSI_CAP_DFD 0x08 /* double feed detection */
+#define FSI_CAP_AFF 0x04 /* auto form feed */
+#define FSI_CAP_ESST 0x02 /* ??? */
+#define FSI_CAP_PED 0x01 /* paper end detection support */
+
+#define FSI_CAP_DPOS_MASK 0x03 /* the document position of the ADF */
+
+/* DPOS status */
+#define FSI_CAP_DPOS_UNKN 0x00 /* no information */
+#define FSI_CAP_DPOS_LEFT 0x01 /* left side position */
+#define FSI_CAP_DPOS_CNTR 0x02 /* center position */
+#define FSI_CAP_DPOS_RIGT 0x03 /* right position */
+
+/* Maintenance requests.
+ */
+#define ESC1_REQ_CLEANING 0x0001
+#define ESC1_REQ_CALIBRATION 0x0002
+#define ESC1_REQ_STATUS 0xffff
+
+#include "device.h"
+
+
+/* Getter commands.
+ */
+SANE_Status cmd_request_identity (device *hw);
+SANE_Status cmd_request_hardware_property (device *hw);
+SANE_Status cmd_request_scanner_status (device *hw);
+SANE_Status cmd_request_extended_identity (device *hw);
+SANE_Status cmd_request_extended_status (device *hw);
+SANE_Status cmd_request_scanning_parameter (device* hw);
+
+
+/* Setter commands.
+ */
+SANE_Status cmd_control_option_unit (device *hw, byte value);
+SANE_Status cmd_set_scanning_parameter (device* hw);
+
+
+/* Action commands.
+ */
+SANE_Status cmd_initialize (device *hw);
+SANE_Status cmd_load_paper (device *hw);
+SANE_Status cmd_eject_paper (device *hw);
+SANE_Status cmd_lock (device *hw);
+SANE_Status cmd_unlock (device *hw);
+SANE_Status cmd_request_scanner_maintenance (device *hw, uint16_t mode);
+
+
+#endif /* !defined (command_h_included) */
diff --git a/backend/defines.h b/backend/defines.h
new file mode 100644
index 0000000..9940b7b
--- /dev/null
+++ b/backend/defines.h
@@ -0,0 +1,104 @@
+/* defines.h -- a grab bag of preprocessor utilities
+ * Copyright (C) 2019 SEIKO EPSON Corporation
+ *
+ * License: EPSON END USER SOFTWARE LICENSE
+ * Author : SEIKO EPSON Corporation
+ *
+ * This file is part of Image Scan! for Linux.
+ * It is distributed under the terms of the EPSON END USER SOFTWARE LICENSE.
+ *
+ * You should have received a verbatim copy of the EPSON END USER SOFTWARE
+ * LICENSE along with the software.
+ */
+
+
+#ifndef defines_h
+#define defines_h
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/* Assorted constants.
+ */
+
+#define MM_PER_INCH 25.4 /* exactly */
+
+
+#ifndef __cplusplus
+/*! A C++ Boolean type and corresponding keywords for our C code.
+ */
+typedef enum {
+ false = 0,
+ true
+} bool;
+#endif
+
+
+/* Run-time contract validation.
+ */
+#include <stdlib.h>
+#include "message.h"
+
+#define _assert(type,condition) \
+ if (!(condition)) \
+ { \
+ err_fatal ("failed: %s (%s)", type, #condition); \
+ exit (EXIT_FAILURE); \
+ }
+#define require(condition) _assert ("require", condition)
+#define promise(condition) _assert ("promise", condition)
+
+
+/* "Typed" memory allocation convenience wrappers.
+ * These are meant to make the invocations consistent and take care of
+ * the casting for you.
+ */
+#include <alloca.h>
+#include <stdlib.h>
+
+#define t_alloca(sz,t) ((t *) alloca ((sz) * sizeof (t)))
+#define t_calloc(sz,t) ((t *) calloc ((sz) , sizeof (t)))
+#define t_malloc(sz,t) ((t *) malloc ((sz) * sizeof (t)))
+#define t_realloc(p,sz,t) ((t *) realloc ((p), (sz) * sizeof (t)))
+
+/* Compute sizes of _statically_ allocated arrays easily.
+ */
+#define num_of(p) (sizeof (p) / sizeof (*p))
+
+/* Safely release acquired resources.
+ * The const_delete() is meant for those rare cases where you need to
+ * clean up const t* typed memory areas.
+ */
+#define delete(p) do { if (p) free (p); p = 0; } while (0)
+#define const_delete(p,t) do { if (p) free ((t) p); p = 0; } while (0)
+
+
+/* Portable path and file name component separators.
+ */
+#ifdef __unix
+#define PATH_SEP_STR ":"
+#define PATH_SEP PATH_SEP_STR[0]
+#define FILE_SEP_STR "/"
+#define FILE_SEP FILE_SEP_STR[0]
+#else
+#define PATH_SEP_STR ";"
+#define PATH_SEP PATH_SEP_STR[0]
+#define FILE_SEP_STR "\\"
+#define FILE_SEP FILE_SEP_STR[0]
+#endif
+
+
+ typedef unsigned char byte;
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* !defined (defines_h) */
diff --git a/backend/device.c b/backend/device.c
new file mode 100644
index 0000000..13fdc65
--- /dev/null
+++ b/backend/device.c
@@ -0,0 +1,563 @@
+/* device.c -- physical device representation
+ * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+/*! \file
+ \brief Implements a hardware device object.
+
+ The hardware device object is built on top of the ESC/I commands.
+ Whereas the commands are only responsible for the I/O details and
+ the encoding and decoding of parameters, hardware device objects
+ handle the protocol logic and keep state.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+
+#include "command.h"
+#include "hw-data.h"
+#include "utils.h"
+
+
+/*! Releases all resources associated with a scanner device.
+ *
+ * The channel will be closed if open and acquired memory is returned
+ * to the operating system.
+ */
+device *
+dev_dtor (device *hw)
+{
+ if (!hw) return hw;
+
+ hw->channel = hw->channel->dtor (hw->channel);
+
+ delete (hw->fbf);
+ delete (hw->adf);
+ delete (hw->tpu);
+ delete (hw->fw_name);
+ if (hw->res_y.list != hw->res.list
+ && hw->res_y.list != hw->res_x.list)
+ {
+ delete (hw->res_y.list);
+ }
+ if (hw->res_x.list != hw->res.list)
+ {
+ delete (hw->res_x.list);
+ }
+ delete (hw->res.list);
+ delete (hw->resolution.list);
+ delete (hw);
+
+ return hw;
+}
+
+/*! Updates the extended status of a hardware device object.
+ */
+SANE_Status
+dev_request_extended_status (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ if (hw->using_fs)
+ {
+ status = cmd_request_scanner_status (hw);
+ if (SANE_STATUS_GOOD != status) return status;
+
+ status = cmd_request_extended_identity (hw);
+ if (SANE_STATUS_GOOD != status) return status;
+
+ status = cmd_request_scanner_status (hw);
+ }
+ else
+ status = cmd_request_extended_status (hw);
+
+ return status;
+}
+
+#include <string.h>
+/*! Loads a sheet on a page type ADF extension.
+ */
+SANE_Status
+dev_load_paper (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ if (!hw->adf) return status; /* guard clauses */
+ if (!(ADF_STATUS_IST & hw->adf->status)) return status;
+ if (!(ADF_STATUS_EN & hw->adf->status)) return status;
+ hw->adf->sheet_count++;
+ if (!(EXT_STATUS_ADFT & hw->ext_status)) return status;
+
+ log_call ();
+
+ status = cmd_load_paper (hw);
+ if (SANE_STATUS_GOOD != status)
+ {
+ hw->adf->sheet_count--;
+ return status;
+ }
+ log_info ("loaded sheet #%d", hw->adf->sheet_count);
+
+ status = dev_request_extended_status (hw);
+
+ if (ADF_STATUS_PE & hw->adf->status
+ && adf_early_paper_end_kills_scan (hw))
+ { /* so we can scan the last sheet */
+ cmd_control_option_unit (hw, 0x00);
+ hw->adf->status &= ~ ADF_STATUS_EN;
+ }
+
+ /* Clear the ADF_STATUS_PE bit. We just successfully loaded a
+ sheet. Also update the ADF_STATUS_ERR bit.
+ */
+ hw->adf->status &= ~ADF_STATUS_PE;
+ if ( (ADF_STATUS_PE & hw->adf->status)
+ || (ADF_STATUS_PJ & hw->adf->status)
+ || (ADF_STATUS_OPN & hw->adf->status))
+ hw->adf->status |= ADF_STATUS_ERR;
+ else
+ hw->adf->status &= ~ADF_STATUS_ERR;
+
+ return status;
+}
+
+/*! Ejects sheets from the ADF extension.
+ */
+SANE_Status
+dev_eject_paper (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ if (!hw->adf) return status; /* guard clauses */
+ if (!(ADF_STATUS_IST & hw->adf->status)) return status;
+ if (!(ADF_STATUS_EN & hw->adf->status)) return status;
+
+ log_call ();
+
+ status = cmd_eject_paper (hw);
+ hw->adf->sheet_count = 0;
+
+ return status;
+}
+
+/*! Open the scanner device. Depending on the connection method,
+ * different open functions are called.
+ */
+SANE_Status
+dev_open (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ log_call ();
+ require (hw->channel);
+
+ if (hw->channel->is_open (hw->channel))
+ {
+ log_info ("scanner is already open: fd = %d", hw->channel->fd);
+ return SANE_STATUS_GOOD; /* no need to open the scanner */
+ }
+
+ hw->channel->open (hw->channel, &status);
+
+ return status;
+}
+
+/*! Log extended (FS W) parameter settings
+ */
+SANE_Status
+dev_log_scanning_parameter (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ log_call ();
+
+ byte *buf = hw->param_buf; /* for convenience */
+
+ log_info ("SANE_START: Color: %d", (int) buf[24]);
+ log_info ("SANE_START: Resolution (x, y): (%u, %u)",
+ buf_to_uint32 (buf+0), buf_to_uint32 (buf+4));
+ log_info ("SANE_START: Scan offset (x, y): (%d, %d)",
+ buf_to_uint32 (buf+8), buf_to_uint32 (buf+12));
+ log_info ("SANE_START: Scan size (w, h): (%d, %d)",
+ buf_to_uint32 (buf+16), buf_to_uint32 (buf+20));
+ log_info ("SANE_START: Data format: %d", (int) buf[25]);
+ log_info ("SANE_START: Halftone: %d", (int) buf[32]);
+ log_info ("SANE_START: Brightness: %d", (int) buf[30]);
+ log_info ("SANE_START: Gamma: %d", (int) buf[29]);
+ log_info ("SANE_START: Color correction: %d", (int) buf[31]);
+ log_info ("SANE_START: Sharpness control: %d", (int) buf[35]);
+ log_info ("SANE_START: Scanning mode: %d", (int) buf[27]);
+ log_info ("SANE_START: Mirroring: %d", (int) buf[36]);
+ log_info ("SANE_START: Auto area segmentation: %d", (int) buf[34]);
+ log_info ("SANE_START: Threshold: %d", (int) buf[33]);
+ log_info ("SANE_START: Line counter: %d", (int) buf[28]);
+ log_info ("SANE_START: Option unit control: %d", (int) buf[26]);
+ log_info ("SANE_START: Film type: %d", (int) buf[37]);
+
+ return status;
+}
+
+/*! Obtain the offset and size of the parameter corresponding
+ * to the given ESC command in the FS W packet structure.
+ * Return true if the given command is in the FS W packet
+ * structure and obtaining the parameter info succeeded,
+ * otherwise return false.
+ */
+static bool
+get_extended_param_info (byte cmd, short *offset, short *size)
+{
+ if (NULL == offset) return false;
+ if (NULL == size) return true;
+
+ *offset = 0;
+ *size = 1; /* default size of most parameters */
+
+ if ('R' == cmd) { *offset = 0; *size = 8; }
+ else if ('A' == cmd) { *offset = 8; *size = 16; }
+ else if ('C' == cmd) *offset = 24;
+ else if ('D' == cmd) *offset = 25;
+ else if ('e' == cmd) *offset = 26;
+ else if ('g' == cmd) *offset = 27;
+ else if ('d' == cmd) *offset = 28;
+ else if ('Z' == cmd) *offset = 29;
+ else if ('L' == cmd) *offset = 30;
+ else if ('M' == cmd) *offset = 31;
+ else if ('B' == cmd) *offset = 32;
+ else if ('t' == cmd) *offset = 33;
+ else if ('s' == cmd) *offset = 34;
+ else if ('Q' == cmd) *offset = 35;
+ else if ('K' == cmd) *offset = 36;
+ else if ('N' == cmd) *offset = 37;
+ else *size = 0;
+
+ if (0 == *size) return false;
+ return true;
+}
+
+/*! Copies a number of bytes from \a param into the FS W packet buffer.
+ * The size and offset are looked up based on the \a cmd passed.
+ */
+SANE_Status
+dev_set_scanning_parameter (device *hw, byte cmd, const byte* param)
+{
+ short offset = 0;
+ short size = 0;
+
+ log_call ();
+ require (hw);
+
+ if (NULL == param) return SANE_STATUS_INVAL;
+ if (!get_extended_param_info (cmd, &offset, &size)) return SANE_STATUS_INVAL;
+
+ memcpy (hw->param_buf + offset, param, size);
+
+ return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+dev_set_scanning_resolution (device *hw, SANE_Int x_dpi, SANE_Int y_dpi)
+{
+ byte buf[8];
+ uint32_t x;
+ uint32_t y;
+
+ log_call ();
+ require (hw);
+
+ if (x_dpi < 0 || x_dpi > UINT32_MAX) return SANE_STATUS_INVAL;
+ if (y_dpi < 0 || y_dpi > UINT32_MAX) return SANE_STATUS_INVAL;
+
+ x = x_dpi;
+ y = y_dpi;
+
+ uint32_to_buf (x, buf + 0);
+ uint32_to_buf (y, buf + 4);
+
+ return dev_set_scanning_parameter (hw, 'R', buf);
+}
+
+SANE_Status
+dev_set_scanning_area (device *hw, SANE_Int left, SANE_Int top,
+ SANE_Int width, SANE_Int height)
+{
+ byte buf[16];
+ uint32_t nx;
+ uint32_t ny;
+ uint32_t nw;
+ uint32_t nh;
+
+ log_call ();
+ require (hw);
+
+ if (left > UINT32_MAX) return SANE_STATUS_INVAL;
+ if (top > UINT32_MAX) return SANE_STATUS_INVAL;
+ if (width > UINT32_MAX) return SANE_STATUS_INVAL;
+ if (height > UINT32_MAX) return SANE_STATUS_INVAL;
+
+ nx = left;
+ ny = top;
+ nw = width;
+ nh = height;
+
+ uint32_to_buf (nx, buf + 0);
+ uint32_to_buf (ny, buf + 4);
+ uint32_to_buf (nw, buf + 8);
+ uint32_to_buf (nh, buf + 12);
+
+ return dev_set_scanning_parameter (hw, 'A', buf);
+}
+
+SANE_Status
+dev_set_option_unit (device *hw, byte adf_mode)
+{
+ byte val = 0;
+
+ log_call ();
+ require (hw);
+
+ val = (using (hw, fbf) ? 0 : 1);
+ if (hw->adf && 1 == val)
+ val += adf_mode; /* set simplex/duplex */
+
+ return dev_set_scanning_parameter (hw, 'e', &val);
+}
+
+static void
+limit_res_list (resolution_info *res, int limit)
+{
+ int i = 0;
+ int new_size = 0;
+
+ for(i=1; i<res->size; i++) // first entry is the list size, skip it
+ {
+ if (res->list[i] > limit) break;
+ ++new_size;
+ }
+
+ res->list[0] = new_size;
+ res->size = new_size;
+ res->last = 0;
+
+ log_info ("Limit resolution to %ddpi", res->list[res->size]);
+}
+
+void
+dev_limit_res (device *self, SANE_Constraint_Type type, int limit)
+{
+ if (SANE_CONSTRAINT_RANGE == type)
+ {
+ self->old_max = self->dpi_range.max;
+ self->dpi_range.max = limit;
+ }
+ else
+ {
+ self->old_max = self->res.size;
+ limit_res_list (&self->res, limit);
+ limit_res_list (&self->res_x, limit);
+ limit_res_list (&self->res_y, limit);
+ }
+}
+
+void
+dev_restore_res (device *self, SANE_Constraint_Type type)
+{
+ if (0 == self->old_max) return;
+
+ if (SANE_CONSTRAINT_RANGE == type)
+ {
+ self->dpi_range.max = self->old_max;
+ }
+ else
+ {
+ // res_x and res_y are recreated, only restore res
+ self->res.size = self->old_max;
+ self->res.list[0] = self->old_max;
+ self->res.last = 0;
+ }
+}
+
+/*! Sends a cancel to the device when not in the middle of retrieving scan data.
+ Return true if a cancel command was successfully sent, false, if not.
+ */
+bool
+dev_force_cancel (device *self)
+{
+ u_char buf[14];
+ u_char params[2];
+ u_char *dummy = NULL;
+ uint32_t block_size = 0;
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ if (!self->using_fs) return false;
+
+ params[0] = FS;
+ params[1] = self->cmd->start_scanning;
+
+ channel_send (self->channel, params, 2, &status);
+ if (SANE_STATUS_GOOD != status) return false;
+
+ channel_recv (self->channel, buf, num_of (buf), &status);
+ if (SANE_STATUS_GOOD != status) return false;
+ if (STX != buf[0]) return false;
+
+ block_size = buf_to_uint32 (buf + 2);
+
+ dummy = t_malloc (block_size, u_char);
+ if (dummy == NULL)
+ {
+ err_fatal ("%s", strerror (errno));
+ return false;
+ }
+
+ channel_recv_all (self->channel, dummy,
+ block_size, &status);
+ delete (dummy);
+
+ if (SANE_STATUS_GOOD != status) return false;
+
+ buf[0] = CAN;
+ channel_send (self->channel, buf, 1, &status);
+ if (SANE_STATUS_GOOD != status) return false;
+
+ channel_recv (self->channel, buf, 1, &status);
+ if (SANE_STATUS_GOOD != status) return false;
+ if (ACK != buf[0]) return false;
+
+ return true;
+}
+
+SANE_Status
+dev_lock (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ log_call ();
+ require (hw);
+
+ if (!hw->uses_locking) return status;
+ if ( hw->is_locked) return status;
+
+ return cmd_lock (hw);
+}
+
+SANE_Status
+dev_unlock (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ log_call ();
+ require (hw);
+
+ if(!hw->uses_locking) return status;
+ if(!hw->is_locked) return status;
+
+ return cmd_unlock (hw);
+}
+
+static SANE_Status
+dev_maintenance (device *hw, uint16_t mode)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ log_call ("(%04x)", mode);
+ require (hw);
+ if (!maintenance_is_supported (hw))
+ {
+ return SANE_STATUS_UNSUPPORTED;
+ }
+ if ( ESC1_REQ_CLEANING != mode
+ && ESC1_REQ_CALIBRATION != mode)
+ {
+ return SANE_STATUS_INVAL;
+ }
+
+ status = cmd_request_scanner_maintenance (hw, mode);
+ if (SANE_STATUS_GOOD == status)
+ {
+ do
+ {
+ microsleep (hw->polling_time);
+ status = cmd_request_scanner_maintenance (hw, ESC1_REQ_STATUS);
+ } while (SANE_STATUS_DEVICE_BUSY == status);
+ }
+
+ return status;
+}
+
+/* convenience function */
+SANE_Status
+dev_clean (device *hw)
+{
+ return dev_maintenance (hw, ESC1_REQ_CLEANING);
+}
+
+/* convenience function */
+SANE_Status
+dev_calibrate (device *hw)
+{
+ return dev_maintenance (hw, ESC1_REQ_CALIBRATION);
+}
diff --git a/backend/device.h b/backend/device.h
new file mode 100644
index 0000000..739429e
--- /dev/null
+++ b/backend/device.h
@@ -0,0 +1,265 @@
+/* device.h -- physical device representation
+ * Copyright (C) 2008, 2009, 2014 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifndef device_h_included
+#define device_h_included
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+
+#include "channel.h"
+#include "extension.h"
+
+#define DEVNAME_LENGTH 16
+
+
+typedef struct
+{
+ char *level;
+
+ unsigned char request_identity;
+ unsigned char request_identity2; /* new request identity command for Dx command level */
+ unsigned char request_status;
+ unsigned char request_condition;
+ unsigned char set_color_mode;
+ unsigned char start_scanning;
+ unsigned char set_data_format;
+ unsigned char set_resolution;
+ unsigned char set_zoom;
+ unsigned char set_scan_area;
+ unsigned char set_bright;
+ SANE_Range bright_range;
+ unsigned char set_gamma;
+ unsigned char set_halftoning;
+ unsigned char set_color_correction;
+ unsigned char initialize_scanner;
+ unsigned char set_speed; /* B4 and later */
+ unsigned char set_lcount;
+ unsigned char mirror_image; /* B5 and later */
+ unsigned char set_gamma_table; /* B4 and later */
+ unsigned char set_outline_emphasis; /* B4 and later */
+ unsigned char set_dither; /* B4 and later */
+ unsigned char set_color_correction_coefficients; /* B3 and later */
+ unsigned char request_extended_status; /* get extended status from scanner */
+ unsigned char control_an_extension; /* for extension control */
+ unsigned char eject; /* for extension control */
+ unsigned char feed;
+ unsigned char request_push_button_status;
+ unsigned char control_auto_area_segmentation;
+ unsigned char set_film_type; /* for extension control */
+ unsigned char set_exposure_time; /* F5 only */
+ unsigned char set_bay; /* F5 only */
+ unsigned char set_threshold;
+ unsigned char set_focus_position; /* B8 only */
+ unsigned char request_focus_position; /* B8 only */
+} EpsonCmdRec, *EpsonCmd;
+
+typedef struct
+{
+ SANE_Int last;
+ SANE_Int size;
+ SANE_Word *list;
+ SANE_Bool deep;
+} resolution_info;
+
+typedef struct
+{
+ int modelID;
+
+ double color_profile[4][9];
+} EpsonScanHardRec, *EpsonScanHard;
+
+
+/*! Software abstraction of the \e physical device.
+ */
+struct device
+{
+ struct channel *channel; /*<! for all device I/O */
+
+ char cmd_lvl[2+1]; /* +1 for string termination */
+ char version[4+1]; /* +1 for string termination */
+ char *fw_name; /*!< model name reported by firmware */
+
+ SANE_Byte status; /*!< info block status byte */
+ SANE_Byte ext_status;
+ SANE_Byte fsf_status;
+ SANE_Byte fsi_cap_1;
+ SANE_Byte fsi_cap_2;
+ SANE_Byte fsi_cap_3;
+
+ const extension *src; /*!< current document source */
+ fbf_extension *fbf; /*!< \c NULL if not available */
+ adf_extension *adf; /*!< \c NULL if not available */
+ tpu_extension *tpu; /*!< \c NULL if not available */
+
+ /*! \brief Selectable scan sources.
+ * A \c NULL terminated list of up to three scan sources. Which
+ * ones are listed depends on device capabilities.
+ */
+ SANE_String_Const sources[4];
+
+ resolution_info resolution; /* full-blown list */
+ resolution_info res; /* advertised list */
+ SANE_Int max_x; /* in pixels */
+ SANE_Int max_y;
+
+ SANE_Int optical_res; /* resolution in pixels per inch */
+ SANE_Byte sensor_info;
+ SANE_Byte scan_order;
+ SANE_Byte line_dist_x;
+ SANE_Byte line_dist_y;
+ resolution_info res_x;
+ resolution_info res_y;
+
+
+ SANE_Int level;
+ SANE_Range dpi_range;
+
+ SANE_Range matrix_range;
+
+ const int *gamma_type;
+ const SANE_Bool *gamma_user_defined;
+
+ const int *color_type;
+ const SANE_Bool *color_user_defined;
+
+ SANE_Bool color_shuffle; /* does this scanner need color shuffling */
+ SANE_Int maxDepth; /* max. color depth */
+
+ SANE_Int max_line_distance;
+
+ SANE_Bool need_color_reorder;
+ SANE_Bool need_reset_on_source_change;
+
+ SANE_Bool wait_for_button; /* do we have to wait until the scanner button is pressed? */
+
+ SANE_Int doctype;
+
+ unsigned int productID;
+
+ EpsonCmd cmd;
+ const EpsonScanHardRec *scan_hard;
+
+ SANE_Bool using_fs; /*! determines whether to use fs commands */
+
+ SANE_Bool block_mode;
+ uint32_t image_block_size;
+ uint32_t final_block_size;
+ uint32_t block_total; /* does NOT include final block! */
+ uint32_t block_count;
+
+ unsigned char param_buf[64]; /*! holds the FS W parameter buffer */
+
+ SANE_Int scan_width_limit;
+
+ SANE_Int base_res; /*! resolution at which the max scan area is defined */
+
+ SANE_Int old_max; /*! used to restore after limiting resolutions */
+
+ SANE_Int polling_time;
+
+ bool uses_locking;
+ bool is_locked;
+};
+
+typedef struct device device;
+
+device *dev_dtor (device *hw);
+
+SANE_Status dev_request_extended_status (device *hw);
+SANE_Status dev_load_paper (device *hw);
+SANE_Status dev_eject_paper (device *hw);
+SANE_Status dev_open (device *hw);
+
+/* FS W/S related functions */
+SANE_Status dev_log_scanning_parameter (device *hw);
+SANE_Status dev_set_scanning_parameter (device *hw,
+ byte cmd,
+ const byte* param);
+SANE_Status dev_set_scanning_resolution (device *hw, SANE_Int x_dpi,
+ SANE_Int y_dpi);
+SANE_Status dev_set_scanning_area (device *hw,
+ SANE_Int left, SANE_Int top,
+ SANE_Int width, SANE_Int height);
+SANE_Status dev_set_option_unit (device *hw, byte adf_mode);
+
+/* other */
+
+/*! supports workarounds to temporarily limit resolution lists */
+void dev_limit_res (device *self, SANE_Constraint_Type type, int limit);
+
+/*! restore original resolution list after it has been limited */
+void dev_restore_res (device *self, SANE_Constraint_Type type);
+
+bool dev_force_cancel (device *self);
+
+SANE_Status dev_lock (device *hw);
+SANE_Status dev_unlock (device *hw);
+
+SANE_Status dev_clean (device *hw);
+SANE_Status dev_calibrate (device *hw);
+
+#endif /* !defined (device_h_included) */
diff --git a/backend/dip-obj.c b/backend/dip-obj.c
new file mode 100644
index 0000000..021cb01
--- /dev/null
+++ b/backend/dip-obj.c
@@ -0,0 +1,746 @@
+/* dip-obj.c -- digital image processing functionality singleton
+ * Copyright (C) 2011 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+#include <string.h>
+
+#include "dip-obj.h"
+#include "defines.h"
+#include "hw-data.h"
+#include "include/sane/sanei_magic.h"
+#include "ipc.h"
+
+#ifndef ENABLE_SANEI_MAGIC
+#define ENABLE_SANEI_MAGIC 0
+#endif
+
+/*! \brief Constrain a value \a v in the interval \c [lo,hi]
+ */
+#define clamp(v,lo,hi) \
+ ((v) < (lo) \
+ ? (lo) \
+ : ((v) > (hi) \
+ ? (hi) \
+ : (v))) \
+ /**/
+
+typedef struct
+{
+ process *plugin;
+
+ void (*autocrop) ();
+ void (*deskew) (buffer *, int, int);
+
+} dip_type;
+
+static dip_type *dip = NULL;
+
+static void esdip_crop (buffer *buf, const device *hw, unsigned int count,
+ const Option_Value *val);
+static void esdip_turn (buffer *buf, int res_x, int res_y);
+
+static void magic_crop (buffer *buf, int res_x, int res_y);
+static void magic_turn (buffer *buf, int res_x, int res_y);
+
+void *
+dip_init (const char *pkglibdir, SANE_Status *status)
+{
+ SANE_Status s = SANE_STATUS_GOOD;
+
+ log_call ("(%s, %p)", pkglibdir, status);
+
+ if (dip)
+ {
+ err_minor ("been here, done that");
+ if (status) *status = s;
+ return dip;
+ }
+
+ dip = t_calloc (1, dip_type);
+ if (!dip)
+ {
+ if (status) *status = SANE_STATUS_NO_MEM;
+ return dip;
+ }
+
+ dip->plugin = ipc_exec ("esdip", pkglibdir, status);
+
+ if (dip->plugin)
+ {
+ dip->autocrop = esdip_crop;
+ dip->deskew = esdip_turn;
+ }
+ else if (ENABLE_SANEI_MAGIC) /* use free alternative API */
+ {
+ sanei_magic_init ();
+ dip->autocrop = magic_crop;
+ dip->deskew = magic_turn;
+ }
+
+ if (status) *status = s;
+ return dip;
+}
+
+void *
+dip_exit (void *self)
+{
+ log_call ("(%p)", self);
+ require (dip == self);
+
+ if (dip)
+ {
+ if (dip->plugin)
+ {
+ dip->plugin = ipc_kill (dip->plugin);
+ }
+ else
+ {
+ /* sanei_magic_exit, if there was one */
+ }
+ delete (dip);
+ }
+
+ return dip;
+}
+
+bool
+dip_needs_whole_image (const void *self, const Option_Value *val,
+ const SANE_Option_Descriptor *opt)
+{
+ require (dip == self && val);
+
+ /* esdip_deskew and esdip_autocrop do not support this
+ */
+ if (val[OPT_X_RESOLUTION].w != val[OPT_Y_RESOLUTION].w)
+ return false;
+
+ return ((SANE_OPTION_IS_ACTIVE (opt[OPT_DESKEW].cap)
+ && val[OPT_DESKEW].b)
+ || (SANE_OPTION_IS_ACTIVE (opt[OPT_AUTOCROP].cap)
+ && val[OPT_AUTOCROP].b));
+}
+
+void
+dip_apply_LUT (const void *self, const buffer *buf,
+ const LUT *m)
+{
+ require (dip == self && buf && m);
+ require (m->depth == buf->ctx.depth);
+
+ /**/ if (16 == buf->ctx.depth)
+ {
+ uint16_t *p = (uint16_t *) buf->ptr;
+ uint16_t *e = (uint16_t *) buf->end;
+
+ while (p < e)
+ {
+ *p = * (uint16_t *) (m->lut + 2 * *p);
+ ++p;
+ }
+ }
+ else if ( 8 == buf->ctx.depth)
+ {
+ SANE_Byte *p = buf->ptr;
+ SANE_Byte *e = buf->end;
+
+ while (p < e)
+ {
+ *p = m->lut[*p];
+ ++p;
+ }
+ }
+ else
+ err_major ("noop: unsupported bit depth %d", buf->ctx.depth);
+}
+
+void
+dip_apply_LUT_RGB (const void *self, const buffer *buf,
+ const LUT *r, const LUT *g, const LUT *b)
+{
+ require (dip == self && buf && r && g && b);
+ require (r->depth == buf->ctx.depth);
+ require (g->depth == buf->ctx.depth);
+ require (b->depth == buf->ctx.depth);
+
+ if (SANE_FRAME_RGB != buf->ctx.format)
+ {
+ err_minor ("noop: image data not in RGB format");
+ return;
+ }
+
+ /**/ if (16 == buf->ctx.depth)
+ {
+ uint16_t *p = (uint16_t *) buf->ptr;
+ uint16_t *e = (uint16_t *) buf->end;
+
+ while (p < e)
+ {
+ p[0] = * (uint16_t *) (r->lut + 2 * p[0]);
+ p[1] = * (uint16_t *) (g->lut + 2 * p[1]);
+ p[2] = * (uint16_t *) (b->lut + 2 * p[2]);
+ p += 3;
+ }
+ }
+ else if ( 8 == buf->ctx.depth)
+ {
+ SANE_Byte *p = buf->ptr;
+ SANE_Byte *e = buf->end;
+
+ while (p < e)
+ {
+ p[0] = r->lut[p[0]];
+ p[1] = g->lut[p[1]];
+ p[2] = b->lut[p[2]];
+ p += 3;
+ }
+ }
+ else
+ err_major ("noop: unsupported bit depth %d", buf->ctx.depth);
+}
+
+/*! \brief Destroys a LUT object
+ */
+LUT *
+dip_destroy_LUT (const void *self, LUT *m)
+{
+ require (dip == self);
+
+ if (m) delete (m->lut);
+ delete (m);
+
+ return m;
+}
+
+/*! \brief Creates a LUT with an encoding \a gamma
+ *
+ * \sa http://en.wikipedia.org/wiki/Gamma_correction
+ */
+LUT *
+dip_gamma_LUT (const void *self, int depth,
+ double gamma)
+{
+ SANE_Byte *lut;
+ LUT *m;
+
+ size_t i;
+ double max;
+
+ require (dip == self);
+ require (8 == depth || 16 == depth);
+
+ lut = t_malloc ((1 << depth) * (depth / 8), SANE_Byte);
+ m = t_malloc (1, LUT);
+
+ if (!lut || !m)
+ {
+ delete (lut);
+ delete (m);
+ return m;
+ }
+
+ m->lut = lut;
+ m->depth = depth;
+
+ max = (1 << depth) - 1;
+
+ for (i = 0; i < (1 << depth); ++i)
+ {
+ double value = max * pow (i / max, 1 / gamma);
+
+ if (16 == depth)
+ {
+ uint16_t *p = (uint16_t *) lut;
+
+ p[i] = clamp (value, 0, max);
+ }
+ else
+ {
+ lut[i] = clamp (value, 0, max);
+ }
+ }
+
+ return m;
+}
+
+LUT *
+dip_iscan_BCHS_LUT (const void *self, int depth,
+ double brightness, double contrast,
+ double highlight, double shadow)
+{
+ SANE_Byte *lut;
+ LUT *m;
+
+ size_t i;
+ size_t max;
+
+ int32_t b, c, h, s, f;
+
+ require (dip == self);
+ require (-1 <= brightness && brightness <= 1);
+ require (-1 <= contrast && contrast <= 1);
+ require ( 0 <= highlight && highlight <= 1);
+ require ( 0 <= shadow && shadow <= 1);
+ require (8 == depth || 16 == depth);
+
+ lut = t_malloc ((1 << depth) * (depth / 8), SANE_Byte);
+ m = t_malloc (1, LUT);
+ if (!lut || !m)
+ {
+ delete (lut);
+ delete (m);
+ return m;
+ }
+
+ m->lut = lut;
+ m->depth = depth;
+
+ /* Compute algorithm parameters by scaling the double values into
+ * integral values that correspond to the bit depth. Computation
+ * of loop variable independent parts is done by absorption into
+ * existing variables.
+ */
+ f = (1 << (depth - 1)) - 1;
+ s = f * shadow;
+ h = -f * highlight; h += 2 * f + 1;
+ b = f * brightness;
+ c = ((0 > contrast)
+ ? f * contrast
+ : (h - s) / 2 * contrast);
+
+ log_data ("b = %d", b);
+ log_data ("c = %d", c);
+ log_data ("h = %d", h);
+ log_data ("s = %d", s);
+
+ if (2 * c == (h - s)) --c; /* avoid zero division */
+
+ s += c; /* absorb scaled contrast */
+ h -= c; /* absorb scaled contrast */
+ h -= s; /* absorb shifted shadow */
+
+ log_data ("h' = %d", h);
+ log_data ("s' = %d", s);
+
+ max = (1 << depth) - 1;
+ log_data ("max = %zd", max);
+
+ for (i = 0; i < (1 << depth); ++i)
+ {
+ int32_t value = i;
+
+ value -= s;
+ value *= max;
+ value /= h;
+ value += b;
+
+ if (16 == depth)
+ {
+ uint16_t *p = (uint16_t *) lut;
+
+ p[i] = clamp (value, 0, max);
+ }
+ else
+ {
+ lut[i] = clamp (value, 0, max);
+ }
+ }
+ return m;
+}
+
+LUT *
+dip_iscan_BC_LUT (const void *self, int depth,
+ double brightness, double contrast)
+{
+ return dip_iscan_BCHS_LUT (self, depth, brightness, contrast, 0, 0);
+}
+
+/*! \brief Creates a LUT for brightness/contrast manipulations
+ *
+ * Algorithm indirectly taken from the GIMP.
+ *
+ * \sa http://en.wikipedia.org/wiki/Image_editing#Contrast_change_and_brightening
+ */
+LUT *
+dip_gimp_BC_LUT (const void *self, int depth,
+ double brightness, double contrast)
+{
+ SANE_Byte *lut;
+ LUT *m;
+
+ size_t i;
+ double max;
+
+ require (dip == self);
+ require (-1 <= brightness && brightness <= 1);
+ require (-1 <= contrast && contrast <= 1);
+ require (8 == depth || 16 == depth);
+
+ lut = t_malloc ((1 << depth) * (depth / 8), SANE_Byte);
+ m = t_malloc (1, LUT);
+
+ if (!lut || !m)
+ {
+ delete (lut);
+ delete (m);
+ return m;
+ }
+
+ m->lut = lut;
+ m->depth = depth;
+
+ max = (1 << depth) - 1;
+
+ for (i = 0; i < (1 << depth); ++i)
+ {
+ double value = i / max;
+
+ if (brightness < 0.0)
+ value *= 1.0 + brightness;
+ else
+ value += (1 - value) * brightness;
+
+ value = (value - 0.5) * tan ((contrast + 1) * M_PI / 4) + 0.5;
+ value *= max;
+
+ if (16 == depth)
+ {
+ uint16_t *p = (uint16_t *) lut;
+
+ p[i] = clamp (value, 0, max);
+ }
+ else
+ {
+ lut[i] = clamp (value, 0, max);
+ }
+ }
+
+ return m;
+}
+
+void
+dip_flip_bits (const void *self, const buffer *buf)
+{
+ SANE_Byte *p;
+
+ require (dip == self && buf);
+
+ p = buf->ptr;
+ while (p != buf->end)
+ {
+ *p = ~*p;
+ ++p;
+ }
+}
+
+static
+void
+dip_change_GRB_to_RGB_16 (const void *self, const buffer *buf)
+{
+ SANE_Byte *p, tmp;
+
+ require (dip == self && buf && 16 == buf->ctx.depth);
+
+ p = buf->ptr;
+ while (p < buf->end)
+ {
+ tmp = p[0]; /* most significant byte */
+ p[0] = p[2];
+ p[2] = p[0];
+ tmp = p[1]; /* least significant byte */
+ p[1] = p[3];
+ p[3] = tmp;
+
+ p += 6;
+ }
+}
+
+static
+void
+dip_change_GRB_to_RGB_8 (const void *self, const buffer *buf)
+{
+ SANE_Byte *p, tmp;
+
+ require (dip == self && buf && 8 == buf->ctx.depth);
+
+ p = buf->ptr;
+ while (p < buf->end)
+ {
+ tmp = p[0];
+ p[0] = p[1];
+ p[1] = tmp;
+
+ p += 3;
+ }
+}
+
+void
+dip_change_GRB_to_RGB (const void *self, const buffer *buf)
+{
+ require (dip == self && buf);
+
+ if (SANE_FRAME_RGB != buf->ctx.format)
+ return;
+
+ /**/ if (16 == buf->ctx.depth)
+ return dip_change_GRB_to_RGB_16 (self, buf);
+ else if ( 8 == buf->ctx.depth)
+ return dip_change_GRB_to_RGB_8 (self, buf);
+
+ err_major ("unsupported bit depth");
+ return;
+}
+
+/*! \todo Add support for 16 bit color values (#816).
+ */
+void
+dip_apply_color_profile (const void *self, const buffer *buf,
+ const double profile[9])
+{
+ SANE_Int i;
+ SANE_Byte *r_buf, *g_buf, *b_buf;
+ double red, grn, blu;
+
+ SANE_Byte *data;
+ SANE_Int size;
+
+ require (dip == self && buf && profile);
+ require (8 == buf->ctx.depth);
+
+ if (SANE_FRAME_RGB != buf->ctx.format)
+ return;
+
+ data = buf->ptr;
+ size = buf->end - buf->ptr;
+
+ for (i = 0; i < size / 3; i++)
+ {
+ r_buf = data;
+ g_buf = data + 1;
+ b_buf = data + 2;
+
+ red =
+ profile[0] * (*r_buf) + profile[1] * (*g_buf) + profile[2] * (*b_buf);
+ grn =
+ profile[3] * (*r_buf) + profile[4] * (*g_buf) + profile[5] * (*b_buf);
+ blu =
+ profile[6] * (*r_buf) + profile[7] * (*g_buf) + profile[8] * (*b_buf);
+
+ *data++ = clamp (red, 0, 255);
+ *data++ = clamp (grn, 0, 255);
+ *data++ = clamp (blu, 0, 255);
+ }
+}
+
+bool
+dip_has_deskew (const void *self, const device *hw)
+{
+ require (dip == self);
+
+ return (magic_turn == dip->deskew
+ || (esdip_turn == dip->deskew
+ && enable_dip_deskew (hw)));
+}
+
+static
+void
+esdip_turn (buffer *buf, int res_x, int res_y)
+{
+ ipc_dip_parms p;
+
+ require (dip->plugin);
+
+ memset (&p, 0, sizeof (p));
+ memcpy (&p.parms, &buf->ctx, sizeof (p.parms));
+ p.res_x = res_x;
+ p.res_y = res_y;
+
+ ipc_dip_proc (dip->plugin, TYPE_DIP_SKEW_FLAG, &p,
+ &buf->ctx, (void **) &buf->buf);
+
+ buf->cap = buf->ctx.bytes_per_line * buf->ctx.lines;
+ buf->ptr = buf->buf;
+ buf->end = buf->ptr;
+ buf->end += buf->ctx.bytes_per_line * buf->ctx.lines;
+}
+
+static
+void
+magic_turn (buffer *buf, int res_x, int res_y)
+{
+ SANE_Status status;
+ int center_x, center_y;
+ double angle;
+ const int bg_sample = 0xff; /* white */
+
+ require (buf);
+
+ status = sanei_magic_findSkew (&buf->ctx, buf->buf, res_x, res_y,
+ &center_x, &center_y, &angle);
+ if (SANE_STATUS_GOOD == status)
+ {
+ status = sanei_magic_rotate (&buf->ctx, buf->buf,
+ center_x, center_y, -angle, bg_sample);
+ }
+
+ buf->ptr = buf->buf;
+ buf->end = buf->ptr;
+ buf->end += buf->ctx.bytes_per_line * buf->ctx.lines;
+}
+
+void
+dip_deskew (const void *self, const device *hw, unsigned int count,
+ buffer *buf, const Option_Value *val)
+{
+ require (dip == self && buf && val);
+
+ dip->deskew (buf, val[OPT_X_RESOLUTION].w, val[OPT_Y_RESOLUTION].w);
+}
+
+/*! \bug autocrop_max_y() is the \e wrong criterion to limit autocrop
+ * support to selected devices.
+ */
+bool
+dip_has_autocrop (const void *self, const device *hw)
+{
+ require (dip == self);
+
+ return (magic_crop == dip->autocrop
+ || (esdip_crop == dip->autocrop
+ && 0 != autocrop_max_y (hw)));
+}
+
+static
+void
+esdip_crop (buffer *buf, const device *hw, unsigned int count,
+ const Option_Value *val)
+{
+ ipc_dip_parms p;
+
+ require (dip->plugin && hw && hw->fw_name && val);
+
+ memset (&p, 0, sizeof (p));
+ memcpy (&p.parms, &buf->ctx, sizeof (p.parms));
+ p.res_x = val[OPT_X_RESOLUTION].w;
+ p.res_y = val[OPT_Y_RESOLUTION].w;
+ p.gamma = hw->gamma_type[ val[OPT_GAMMA_CORRECTION].w ];
+ p.bside = SANE_FALSE;
+ if (using (hw, adf) && val[OPT_ADF_MODE].w)
+ {
+ p.bside = (0 == count % 2);
+ }
+ strncpy (p.fw_name, hw->fw_name, num_of (p.fw_name));
+
+ ipc_dip_proc (dip->plugin, TYPE_DIP_CROP_FLAG, &p,
+ &buf->ctx, (void **) &buf->buf);
+
+ buf->cap = buf->ctx.bytes_per_line * buf->ctx.lines;
+ buf->ptr = buf->buf;
+ buf->end = buf->ptr;
+ buf->end += buf->ctx.bytes_per_line * buf->ctx.lines;
+}
+
+static
+void
+magic_crop (buffer *buf, int res_x, int res_y)
+{
+ SANE_Status status;
+ int top, left, bottom, right;
+
+ require (buf);
+
+ status = sanei_magic_findEdges (&buf->ctx, buf->buf, res_x, res_y,
+ &top, &bottom, &left, &right);
+ if (SANE_STATUS_GOOD == status)
+ {
+ status = sanei_magic_crop (&buf->ctx, buf->buf,
+ top, bottom, left, right);
+ }
+
+ buf->ptr = buf->buf;
+ buf->end = buf->ptr;
+ buf->end += buf->ctx.bytes_per_line * buf->ctx.lines;
+}
+
+void
+dip_autocrop (const void *self, const device *hw, unsigned int count,
+ buffer *buf, const Option_Value *val)
+{
+ require (dip == self && buf && val);
+
+ /**/ if (esdip_crop == dip->autocrop)
+ {
+ esdip_crop (buf, hw, count, val);
+ }
+ else if (magic_crop == dip->autocrop)
+ {
+ int res_x = val[OPT_X_RESOLUTION].w;
+ int res_y = val[OPT_Y_RESOLUTION].w;
+
+ magic_crop (buf, res_x, res_y);
+ }
+}
diff --git a/backend/dip-obj.h b/backend/dip-obj.h
new file mode 100644
index 0000000..6f010c8
--- /dev/null
+++ b/backend/dip-obj.h
@@ -0,0 +1,119 @@
+/* dip-obj.h -- digital image processing functionality singleton
+ * Copyright (C) 2011 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifndef dip_obj_h_included
+#define dip_obj_h_included
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sane/sane.h>
+
+#include "device.h"
+#include "epkowa.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ void * dip_init (const char *pkglibdir, SANE_Status *status);
+ void * dip_exit (void *self);
+
+ bool dip_needs_whole_image (const void *self, const Option_Value *val,
+ const SANE_Option_Descriptor *opt);
+
+ void dip_apply_LUT (const void *self, const buffer *buf, const LUT *m);
+ void dip_apply_LUT_RGB (const void *self, const buffer *buf,
+ const LUT *r, const LUT *g, const LUT *b);
+ LUT * dip_destroy_LUT (const void *self, LUT *m);
+ LUT * dip_gamma_LUT (const void *self, int depth,
+ double gamma);
+ LUT * dip_iscan_BCHS_LUT (const void *self, int depth,
+ double brightness, double contrast,
+ double highlight, double shadow);
+ LUT * dip_iscan_BC_LUT (const void *self, int depth,
+ double brightness, double contrast);
+ LUT * dip_gimp_BC_LUT (const void *self, int depth,
+ double brightness, double contrast);
+
+ void dip_flip_bits (const void *self, const buffer *buf);
+ void dip_change_GRB_to_RGB (const void *self, const buffer *buf);
+ void dip_apply_color_profile (const void *self, const buffer *buf,
+ const double profile[9]);
+
+ bool dip_has_deskew (const void *self, const device *hw);
+ bool dip_has_autocrop (const void *self, const device *hw);
+
+ void dip_deskew (const void *self, const device *hw, unsigned int count,
+ buffer *buf, const Option_Value *val);
+ void dip_autocrop (const void *self, const device *hw, unsigned int count,
+ buffer *buf, const Option_Value *val);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* !defined (dip_obj_h_included) */
diff --git a/backend/epkowa.c b/backend/epkowa.c
new file mode 100644
index 0000000..14887bb
--- /dev/null
+++ b/backend/epkowa.c
@@ -0,0 +1,6513 @@
+/* epkowa.c - SANE backend for EPSON flatbed scanners
+ (Image Scan! version)
+
+ Based on the SANE Epson backend (originally from sane-1.0.3)
+ - updated to sane-backends-1.0.6
+ - renamed from epson to epkowa to avoid confusion
+ - updated to sane-backends-1.0.12
+ - updated to sane-backends-1.0.15
+
+ Based on Kazuhiro Sasayama previous
+ Work on epson.[ch] file from the SANE package.
+
+ Original code taken from sane-0.71
+ Copyright (C) 1997 Hypercore Software Design, Ltd.
+
+ modifications
+ Copyright (C) 1998-1999 Christian Bucher <bucher@vernetzt.at>
+ Copyright (C) 1998-1999 Kling & Hautzinger GmbH
+ Copyright (C) 1999 Norihiko Sawa <sawa@yb3.so-net.ne.jp>
+ Copyright (C) 2000 Mike Porter <mike@udel.edu> (mjp)
+ Copyright (C) 1999-2004 Karl Heinz Kremer <khk@khk.net>
+ Copyright (C) 2001-2016 SEIKO EPSON CORPORATION
+
+ This file is part of the EPKOWA SANE backend.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+ */
+
+
+#ifndef SANE_I18N
+#define SANE_I18N(text) (text)
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <limits.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <math.h>
+
+#include <sane/saneopts.h>
+
+#ifndef BACKEND_NAME
+#define BACKEND_NAME epkowa
+#endif
+
+#include "backend.h"
+#include "command.h"
+#include "hw-data.h"
+#include "utils.h"
+#include "timing.h"
+#include "cfg-obj.h"
+#include "dip-obj.h"
+#include "model-info.h"
+#include "utils.h"
+
+#include "epkowa_scsi.h"
+#include "sane/sanei_pio.h"
+#include "epkowa_ip.h" /* interpreter-based scanner support */
+
+#include "sane/sanei.h"
+
+#define DEFAULT_RESOLUTION 300 /* dpi */
+#define DEFAULT_X_RESOLUTION DEFAULT_RESOLUTION
+#define DEFAULT_Y_RESOLUTION DEFAULT_RESOLUTION
+
+#define S_ACK "\006"
+#define S_CAN "\030"
+
+/* Usable values when defining EPSON_LEVEL_DEFAULT
+ * There is also a function level "A5", used for the GT-300, a
+ * monochrome only scanner. This level is not supported.
+ */
+#define EPSON_LEVEL_A1 0
+#define EPSON_LEVEL_A2 1
+#define EPSON_LEVEL_B1 2
+#define EPSON_LEVEL_B2 3
+#define EPSON_LEVEL_B3 4
+#define EPSON_LEVEL_B4 5
+#define EPSON_LEVEL_B5 6
+#define EPSON_LEVEL_B6 7
+#define EPSON_LEVEL_B7 8
+#define EPSON_LEVEL_B8 9
+#define EPSON_LEVEL_F5 10
+#define EPSON_LEVEL_D1 11
+#define EPSON_LEVEL_D2 12
+#define EPSON_LEVEL_D7 13
+#define EPSON_LEVEL_D8 14
+
+#define EPSON_LEVEL_DEFAULT EPSON_LEVEL_B3
+
+static EpsonCmdRec epson_cmd[] = {
+/*
+ * request identity
+ * | request identity2
+ * | | request status
+ * | | | request condition
+ * | | | | set color mode
+ * | | | | | start scanning
+ * | | | | | | set data format
+ * | | | | | | | set resolution
+ * | | | | | | | | set zoom
+ * | | | | | | | | | set scan area
+ * | | | | | | | | | | set brightness
+ * | | | | | | | | | | | set gamma
+ * | | | | | | | | | | | | set halftoning
+ * | | | | | | | | | | | | | set color correction
+ * | | | | | | | | | | | | | | initialize scanner
+ * | | | | | | | | | | | | | | | set speed
+ * | | | | | | | | | | | | | | | | set lcount
+ * | | | | | | | | | | | | | | | | | mirror image
+ * | | | | | | | | | | | | | | | | | | set gamma table
+ * | | | | | | | | | | | | | | | | | | | set outline emphasis
+ * | | | | | | | | | | | | | | | | | | | | set dither
+ * | | | | | | | | | | | | | | | | | | | | | set color correction coefficients
+ * | | | | | | | | | | | | | | | | | | | | | | request extension status
+ * | | | | | | | | | | | | | | | | | | | | | | | control an extension
+ * | | | | | | | | | | | | | | | | | | | | | | | | forward feed / eject
+ * | | | | | | | | | | | | | | | | | | | | | | | | | feed
+ * | | | | | | | | | | | | | | | | | | | | | | | | | | request push button status
+ * | | | | | | | | | | | | | | | | | | | | | | | | | | | control auto area segmentation
+ * | | | | | | | | | | | | | | | | | | | | | | | | | | | | set film type
+ * | | | | | | | | | | | | | | | | | | | | | | | | | | | | | set exposure time
+ * | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | set bay
+ * | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | set threshold
+ * | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | set focus position
+ * | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | request focus position
+ * | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
+ * | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
+ */
+ {"A1",'I', 0 ,'F','S', 0 ,'G', 0 ,'R', 0 ,'A', 0 ,{ 0, 0, 0}, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
+ {"A2",'I', 0 ,'F','S', 0 ,'G','D','R','H','A','L',{-3, 3, 0},'Z','B', 0 ,'@', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
+ {"B1",'I', 0 ,'F','S','C','G','D','R', 0 ,'A', 0 ,{ 0, 0, 0}, 0 ,'B', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
+ {"B2",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z','B', 0 ,'@', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
+ {"B3",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z','B','M','@', 0 , 0 , 0 , 0 , 0 , 0 ,'m','f','e', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
+ {"B4",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z','B','M','@','g','d', 0 ,'z','Q','b','m','f','e', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
+ {"B5",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z','B','M','@','g','d','K','z','Q','b','m','f','e', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
+ {"B6",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z','B','M','@','g','d','K','z','Q','b','m','f','e', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 },
+ {"B7",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z','B','M','@','g','d','K','z','Q','b','m','f','e','\f', 0 ,'!','s','N', 0 , 0 ,'t', 0 , 0 },
+ {"B8",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z','B','M','@','g','d','K','z','Q','b','m','f','e','\f', 0x19,'!','s','N', 0 , 0 ,'t','p','q'},
+ {"F5",'I', 0 ,'F','S','C','G','D','R','H','A','L',{-3, 3, 0},'Z', 0 ,'M','@','g','d','K','z','Q', 0 ,'m','f','e','\f', 0 , 0 , 0 ,'N','T','P', 0 , 0 , 0 },
+ {"D1",'I','i','F', 0 ,'C','G','D','R', 0 ,'A', 0 ,{ 0, 0, 0},'Z', 0 , 0 ,'@','g','d', 0 ,'z', 0 , 0 , 0 ,'f', 0 , 0 , 0 ,'!', 0 , 0 , 0 , 0 ,'t', 0 , 0 },
+ {"D2",'I','i','F', 0 ,'C','G','D','R', 0 ,'A', 0 ,{ 0, 0, 0},'Z', 0 , 0 ,'@','g','d', 0 ,'z', 0 , 0 , 0 ,'f','e', 0 , 0 ,'!', 0 ,'N', 0 , 0 ,'t', 0 , 0 },
+ {"D7",'I','i','F', 0 ,'C','G','D','R', 0 ,'A', 0 ,{ 0, 0, 0},'Z', 0 , 0 ,'@','g','d', 0 ,'z', 0 , 0 , 0 ,'f','e','\f', 0 ,'!', 0 ,'N', 0 , 0 ,'t', 0 , 0 },
+ {"D8",'I','i','F', 0 ,'C','G','D','R', 0 ,'A', 0 ,{ 0, 0, 0},'Z', 0 , 0 ,'@','g','d', 0 ,'z', 0 , 0 , 0 ,'f','e','\f', 0 ,'!', 0 ,'N', 0 , 0 ,'t', 0 , 0 },
+};
+
+
+/* Definition of the mode_param struct, that is used to
+ * specify the valid parameters for the different scan modes.
+ *
+ * The depth variable gets updated when the bit depth is modified.
+ */
+
+struct mode_param
+{
+ int color;
+ int mode_flags;
+ int dropout_mask;
+ int depth;
+};
+
+static struct mode_param mode_params[] = {
+ {0, 0x00, 0x30, 1},
+ {0, 0x00, 0x30, 8},
+ {1, 0x02, 0x00, 8}
+};
+
+static const SANE_String_Const mode_list[] = {
+ SANE_I18N ("Binary"),
+ SANE_I18N ("Gray"),
+ SANE_I18N ("Color"),
+ NULL
+};
+
+static const SANE_String_Const adf_mode_list[] = {
+ SANE_I18N ("Simplex"),
+ SANE_I18N ("Duplex"),
+ NULL
+};
+
+static const SANE_String_Const dfd_sensitivity[] = {
+ SANE_I18N ("None"),
+ SANE_I18N ("Low"),
+ SANE_I18N ("High"),
+ NULL
+};
+
+/* Define the different scan sources:
+ */
+
+#define FBF_STR SANE_I18N("Flatbed")
+#define TPU_STR SANE_I18N("Transparency Unit")
+#define ADF_STR SANE_I18N("Automatic Document Feeder")
+
+#define FILM_TYPE_POSITIVE (0)
+#define FILM_TYPE_NEGATIVE (1)
+
+static const SANE_String_Const film_list[] = {
+ SANE_I18N ("Positive Film"),
+ SANE_I18N ("Negative Film"),
+ NULL
+};
+
+static const SANE_String_Const focus_list[] = {
+ SANE_I18N ("Focus on glass"),
+ SANE_I18N ("Focus 2.5mm above glass"),
+ NULL
+};
+
+#define HALFTONE_NONE 0x01
+#define HALFTONE_TET 0x03
+
+static int halftone_params[] = {
+ HALFTONE_NONE,
+ 0x00,
+ 0x10,
+ 0x20,
+ 0x80,
+ 0x90,
+ 0xa0,
+ 0xb0,
+ HALFTONE_TET,
+ 0xc0,
+ 0xd0
+};
+
+static const SANE_String_Const halftone_list[] = {
+ SANE_I18N ("None"),
+ SANE_I18N ("Halftone A (Hard Tone)"),
+ SANE_I18N ("Halftone B (Soft Tone)"),
+ SANE_I18N ("Halftone C (Net Screen)"),
+ NULL
+};
+
+static const SANE_String_Const halftone_list_4[] = {
+ SANE_I18N ("None"),
+ SANE_I18N ("Halftone A (Hard Tone)"),
+ SANE_I18N ("Halftone B (Soft Tone)"),
+ SANE_I18N ("Halftone C (Net Screen)"),
+ SANE_I18N ("Dither A (4x4 Bayer)"),
+ SANE_I18N ("Dither B (4x4 Spiral)"),
+ SANE_I18N ("Dither C (4x4 Net Screen)"),
+ SANE_I18N ("Dither D (8x4 Net Screen)"),
+ NULL
+};
+
+static const SANE_String_Const halftone_list_7[] = {
+ SANE_I18N ("None"),
+ SANE_I18N ("Halftone A (Hard Tone)"),
+ SANE_I18N ("Halftone B (Soft Tone)"),
+ SANE_I18N ("Halftone C (Net Screen)"),
+ SANE_I18N ("Dither A (4x4 Bayer)"),
+ SANE_I18N ("Dither B (4x4 Spiral)"),
+ SANE_I18N ("Dither C (4x4 Net Screen)"),
+ SANE_I18N ("Dither D (8x4 Net Screen)"),
+ SANE_I18N ("Text Enhanced Technology"),
+ SANE_I18N ("Download pattern A"),
+ SANE_I18N ("Download pattern B"),
+ NULL
+};
+
+static int dropout_params[] = {
+ 0x00, /* none */
+ 0x10, /* red */
+ 0x20, /* green */
+ 0x30 /* blue */
+};
+
+static const SANE_String_Const dropout_list[] = {
+ SANE_I18N ("None"),
+ SANE_I18N ("Red"),
+ SANE_I18N ("Green"),
+ SANE_I18N ("Blue"),
+ NULL
+};
+
+static const SANE_String_Const brightness_method_list[] = {
+ SANE_I18N ("hardware"), /* needs to be first */
+ SANE_I18N ("iscan"),
+ SANE_I18N ("gimp"),
+ NULL
+};
+
+#define max_val 100 /* any integer value will do */
+static const SANE_Range brightness_range = { -max_val, max_val, 1};
+static const SANE_Range contrast_range = { -max_val, max_val, 1};
+#if 0
+/* We don't provide highlight and shadow options yet because we
+ * haven't worked out the GIMP part of that and how that should
+ * interact with the brightness-method option. For the "iscan"
+ * method, the values below are OK.
+ */
+static const SANE_Range highlight_range = { 0, max_val, 0 };
+static const SANE_Range shadow_range = { 0, max_val, 0 };
+#endif
+#undef max_val
+
+/* Color correction:
+ * One array for the actual parameters that get sent to the scanner (color_params[]),
+ * one array for the strings that get displayed in the user interface (color_list[])
+ * and one array to mark the user defined color correction (dolor_userdefined[]).
+ */
+
+static int color_params_ab[] = {
+ 0x00,
+ 0x01,
+ 0x10,
+ 0x20,
+ 0x40,
+ 0x80
+};
+
+static SANE_Bool color_userdefined_ab[] = {
+ SANE_FALSE,
+ SANE_TRUE,
+ SANE_FALSE,
+ SANE_FALSE,
+ SANE_FALSE,
+ SANE_FALSE
+};
+
+static const SANE_String_Const color_list_ab[] = {
+ SANE_I18N ("No Correction"),
+ SANE_I18N ("User defined"),
+ SANE_I18N ("Impact-dot printers"),
+ SANE_I18N ("Thermal printers"),
+ SANE_I18N ("Ink-jet printers"),
+ SANE_I18N ("CRT monitors"),
+ NULL
+};
+
+static int color_params_d[] = {
+ 0x00
+};
+
+static SANE_Bool color_userdefined_d[] = {
+ SANE_TRUE
+};
+
+static const SANE_String_Const color_list_d[] = {
+ "User defined",
+ NULL
+};
+
+
+/* Gamma correction:
+ * The A and B level scanners work differently than the D level scanners, therefore
+ * I define two different sets of arrays, plus one set of variables that get set to
+ * the actally used params and list arrays at runtime.
+ */
+
+static int gamma_params_ab[] = {
+ 0x01,
+ 0x03,
+ 0x04,
+ 0x00,
+ 0x10,
+ 0x20
+};
+
+static const SANE_String_Const gamma_list_ab[] = {
+ SANE_I18N ("Default"),
+ SANE_I18N ("User defined (Gamma=1.0)"),
+ SANE_I18N ("User defined (Gamma=1.8)"),
+ SANE_I18N ("High density printing"),
+ SANE_I18N ("Low density printing"),
+ SANE_I18N ("High contrast printing"),
+ NULL
+};
+
+static SANE_Bool gamma_userdefined_ab[] = {
+ SANE_FALSE,
+ SANE_TRUE,
+ SANE_TRUE,
+ SANE_FALSE,
+ SANE_FALSE,
+ SANE_FALSE,
+};
+
+static int gamma_params_d[] = {
+ 0x03,
+ 0x04
+};
+
+static const SANE_String_Const gamma_list_d[] = {
+ SANE_I18N ("User defined (Gamma=1.0)"),
+ SANE_I18N ("User defined (Gamma=1.8)"),
+ NULL
+};
+
+static SANE_Bool gamma_userdefined_d[] = {
+ SANE_TRUE,
+ SANE_TRUE
+};
+
+
+/* Bay list:
+ * this is used for the FilmScan
+ */
+
+static const SANE_String_Const bay_list[] = {
+ " 1 ",
+ " 2 ",
+ " 3 ",
+ " 4 ",
+ " 5 ",
+ " 6 ",
+ NULL
+};
+
+static const SANE_Range u8_range = { 0, 255, 0 };
+static const SANE_Range s8_range = { -127, 127, 0 };
+static const SANE_Range zoom_range = { 50, 200, 0 };
+
+/* The "switch_params" are used for several boolean choices
+ */
+static int switch_params[] = {
+ 0,
+ 1
+};
+
+#define mirror_params switch_params
+#define speed_params switch_params
+#define film_params switch_params
+
+static const SANE_Range outline_emphasis_range = { -2, 2, 0 };
+
+enum
+ {
+ EXT_SANE_STATUS_NONE,
+ EXT_SANE_STATUS_MULTI_FEED,
+ EXT_SANE_STATUS_TRAY_CLOSED,
+ EXT_SANE_STATUS_MAX,
+ };
+static const SANE_Range ext_sane_status = {
+ EXT_SANE_STATUS_NONE,
+ EXT_SANE_STATUS_MAX - 1,
+ 0
+};
+
+typedef struct {
+ double width; /* in mm */
+ double height; /* in mm */
+ SANE_String_Const name;
+} media_type;
+
+static SANE_String_Const media_maximum = SANE_I18N ("Maximum");
+static SANE_String_Const media_automatic = SANE_I18N ("Automatic");
+
+/*! \brief List of preset media sizes
+ * This list is used to populate the constraint list for the
+ * scan-area option.
+ *
+ * \remark When adding Landscape and Portrait variants of a medium
+ * size, make sure to add three(!) entries using the same
+ * pattern as used for those already listed. The algorithm
+ * used to populate the constraint list depends on this
+ * convention to create the most user-friendly list.
+ */
+static const media_type media_list[] = {
+ /* ISO A Series */
+ { 297, 420, SANE_I18N ("A3") },
+ { 297, 210, SANE_I18N ("A4 Landscape") },
+ { 210, 297, SANE_I18N ("A4 Portrait") },
+ { 210, 297, SANE_I18N ("A4") },
+ { 210, 148, SANE_I18N ("A5 Landscape") },
+ { 148, 210, SANE_I18N ("A5 Portrait") },
+ { 148, 210, SANE_I18N ("A5") },
+ /* JIS B Series */
+ { 257, 364, SANE_I18N ("B4") },
+ { 257, 184, SANE_I18N ("B5 Landscape") },
+ { 184, 257, SANE_I18N ("B5 Portrait") },
+ { 184, 257, SANE_I18N ("B5") },
+ /* North American sizes */
+ { 11.00 * MM_PER_INCH, 17.00 * MM_PER_INCH, SANE_I18N ("Ledger") },
+ { 8.50 * MM_PER_INCH, 14.00 * MM_PER_INCH, SANE_I18N ("Legal") },
+ { 11.00 * MM_PER_INCH, 8.50 * MM_PER_INCH, SANE_I18N ("Letter Landscape") },
+ { 8.50 * MM_PER_INCH, 11.00 * MM_PER_INCH, SANE_I18N ("Letter Portrait") },
+ { 8.50 * MM_PER_INCH, 11.00 * MM_PER_INCH, SANE_I18N ("Letter") },
+ { 10.50 * MM_PER_INCH, 7.25 * MM_PER_INCH, SANE_I18N ("Executive Landscape") },
+ { 7.25 * MM_PER_INCH, 10.50 * MM_PER_INCH, SANE_I18N ("Executive Portrait") },
+ { 7.25 * MM_PER_INCH, 10.50 * MM_PER_INCH, SANE_I18N ("Executive") },
+ /* Miscellaneous */
+ { 120, 120, SANE_I18N ("CD")},
+};
+
+
+static SANE_Word *bitDepthList = NULL;
+
+
+/* Some utility macros
+ */
+
+/*! Returns the larger of the arguments \a a and \a b.
+ */
+#define max(a, b) ((a) < (b) ? (b) : (a))
+
+/*! Returns the smaller of the arguments \a a and \a b.
+ */
+#define min(a, b) ((a) < (b) ? (a) : (b))
+
+
+static size_t
+max_string_size (const SANE_String_Const strings[])
+{
+ size_t size, max_size = 0;
+ int i;
+
+ for (i = 0; strings[i]; i++)
+ {
+ size = strlen (strings[i]) + 1;
+ if (size > max_size)
+ max_size = size;
+ }
+ return max_size;
+}
+
+
+static inline
+bool
+need_autocrop_override (const Epson_Scanner *s)
+{
+ return (SANE_OPTION_IS_ACTIVE (s->opt[OPT_AUTOCROP].cap)
+ && s->val[OPT_AUTOCROP].b
+ && 0 != autocrop_max_y (s->hw));
+}
+
+typedef struct
+{
+ u_char code;
+ u_char status;
+ u_char count1;
+ u_char count2;
+
+ u_char type;
+ u_char level;
+
+ u_char buf[1];
+
+} EpsonIdentRec, *EpsonIdent;
+
+typedef struct
+{
+ u_char code;
+ u_char status;
+ u_char count1;
+ u_char count2;
+ u_char buf[1];
+
+} EpsonHdrRec, *EpsonHdr;
+
+
+static SANE_Status
+read_image_info_block (device *hw);
+
+static SANE_Status
+get_identity_information (device *hw);
+
+static SANE_Status
+get_hardware_property (device *hw);
+
+static SANE_Status
+get_identity2_information (device *hw, Epson_Scanner *s);
+
+static SANE_Status
+check_warmup (device *hw);
+
+static SANE_Status
+check_ext_status (device *hw);
+
+SANE_Status
+control_option_unit (device *hw, SANE_Bool use_duplex);
+
+static SANE_Status
+initialize (device *hw);
+
+static SANE_Status
+get_resolution_constraints (device *hw, Epson_Scanner *s);
+
+static SANE_Status
+get_push_button_status (device *hw, SANE_Bool *button_pushed);
+
+static SANE_Status color_shuffle (Epson_Scanner *s, int *new_length);
+static void activateOption (Epson_Scanner * s, SANE_Int option,
+ SANE_Bool * change);
+static void deactivateOption (Epson_Scanner * s, SANE_Int option,
+ SANE_Bool * change);
+static void setOptionState (Epson_Scanner * s, SANE_Bool state,
+ SANE_Int option, SANE_Bool * change);
+static void filter_resolution_list (Epson_Scanner * s);
+static void scan_finish (Epson_Scanner * s);
+
+static void get_colorcoeff_from_profile (double *profile,
+ unsigned char *color_coeff);
+static void round_cct (double org_cct[], int rnd_cct[]);
+static int get_roundup_index (double frac[], int n);
+static int get_rounddown_index (double frac[], int n);
+
+static void handle_mode (Epson_Scanner * s, SANE_Int optindex,
+ SANE_Bool * reload);
+static void handle_resolution (Epson_Scanner * s, SANE_Int option,
+ SANE_Word value);
+static void change_profile_matrix (Epson_Scanner * s);
+static void handle_depth_halftone (Epson_Scanner * s, SANE_Int optindex,
+ SANE_Bool * reload);
+
+static SANE_Status create_epson_device (device **dev, channel* ch);
+static SANE_Status create_epson_scanner (device *dev, Epson_Scanner **scanner);
+static SANE_Status create_sane_handle (Epson_Scanner **scanner, const char *name, const void *dip);
+static SANE_Status init_options (Epson_Scanner * s);
+
+static scan_area_t get_model_info_max_scan_area (device *hw, int adf_mode);
+static SANE_Status handle_scan_area(Epson_Scanner *s, int adf_mode);
+
+static SANE_Status handle_source (Epson_Scanner * s,
+ SANE_Int optindex,
+ char *value);
+
+static void adf_handle_out_of_paper (Epson_Scanner * s);
+static void adf_handle_adjust_alignment (Epson_Scanner *s, SANE_Bool finalize);
+
+static void handle_autocrop (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload);
+static void handle_deskew (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload);
+static void handle_detect_doc_size (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload);
+static void handle_preview (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload);
+
+static SANE_Status
+expect_ack (device *hw)
+{
+ u_char result[1];
+ size_t len;
+ SANE_Status status;
+
+ log_call ();
+
+ len = sizeof (result);
+
+ channel_recv (hw->channel, result, len, &status);
+
+ if (SANE_STATUS_GOOD != status)
+ return status;
+
+ if (ACK != result[0])
+ return SANE_STATUS_INVAL;
+
+ return SANE_STATUS_GOOD;
+}
+
+
+static SANE_Status
+set_cmd (device *hw, u_char cmd, u_char val)
+{
+ SANE_Status status;
+ u_char params[2];
+
+ if (!cmd)
+ return SANE_STATUS_UNSUPPORTED;
+
+ log_call ("(%c)", cmd);
+
+ /* Override with extended parameter command (FS W).
+ * Do not override A, R and e, they are handled separately.
+ */
+ if (hw->using_fs && strchr ("CDgdZLMBtsQKN", cmd))
+ return dev_set_scanning_parameter (hw, cmd, &val);
+
+ params[0] = ESC;
+ params[1] = cmd;
+
+ channel_send (hw->channel, params, 2, &status);
+ if (SANE_STATUS_GOOD != (status = expect_ack (hw)))
+ return status;
+
+ params[0] = val;
+ channel_send (hw->channel, params, 1, &status);
+ status = expect_ack (hw);
+
+ return status;
+}
+
+static void
+print_params (const SANE_Parameters params)
+{
+ log_data ("params.format = %d", params.format);
+ log_data ("params.last_frame = %d", params.last_frame);
+ log_data ("params.bytes_per_line = %d", params.bytes_per_line);
+ log_data ("params.pixels_per_line = %d", params.pixels_per_line);
+ log_data ("params.lines = %d", params.lines);
+ log_data ("params.depth = %d", params.depth);
+}
+
+
+#define set_focus_position(h,v) set_cmd (h,(h)->cmd->set_focus_position,v)
+#define set_color_mode(h,v) set_cmd (h,(h)->cmd->set_color_mode,v)
+#define set_data_format(h,v) set_cmd (h,(h)->cmd->set_data_format, v)
+#define set_halftoning(h,v) set_cmd (h,(h)->cmd->set_halftoning, v)
+#define set_gamma(h,v) set_cmd (h,(h)->cmd->set_gamma, v)
+#define set_color_correction(h,v) set_cmd (h,(h)->cmd->set_color_correction, v)
+#define set_lcount(h,v) set_cmd (h,(h)->cmd->set_lcount, v)
+#define set_bright(h,v) set_cmd (h,(h)->cmd->set_bright, v)
+#define mirror_image(h,v) set_cmd (h,(h)->cmd->mirror_image, v)
+#define set_speed(h,v) set_cmd (h,(h)->cmd->set_speed, v)
+#define set_outline_emphasis(h,v) set_cmd (h,(h)->cmd->set_outline_emphasis, v)
+#define control_auto_area_segmentation(h,v) set_cmd (h,(h)->cmd->control_auto_area_segmentation, v)
+#define set_film_type(h,v) set_cmd (h,(h)->cmd->set_film_type, v)
+#define set_exposure_time(h,v) set_cmd (h,(h)->cmd->set_exposure_time, v)
+#define set_bay(h,v) set_cmd (h,(h)->cmd->set_bay, v)
+#define set_threshold(h,v) set_cmd (h,(h)->cmd->set_threshold, v)
+
+
+static SANE_Status
+set_zoom (device *hw, int x_zoom, int y_zoom)
+{
+ SANE_Status status;
+ u_char cmd[2];
+ u_char params[2];
+
+ if (!hw->cmd->set_zoom)
+ return SANE_STATUS_GOOD;
+
+ log_call ();
+
+ cmd[0] = ESC;
+ cmd[1] = hw->cmd->set_zoom;
+
+ channel_send (hw->channel, cmd, 2, &status);
+ status = expect_ack (hw);
+
+ if (status != SANE_STATUS_GOOD)
+ return status;
+
+ params[0] = x_zoom;
+ params[1] = y_zoom;
+
+ channel_send (hw->channel, params, 2, &status);
+ status = expect_ack (hw);
+
+ return status;
+}
+
+
+static SANE_Status
+set_resolution (device *hw, int xres, int yres)
+{
+ SANE_Status status;
+ u_char params[4];
+
+ if (!hw->cmd->set_resolution)
+ return SANE_STATUS_GOOD;
+
+ log_call ();
+
+ params[0] = ESC;
+ params[1] = hw->cmd->set_resolution;
+
+ channel_send (hw->channel, params, 2, &status);
+ status = expect_ack (hw);
+
+ if (status != SANE_STATUS_GOOD)
+ return status;
+
+ params[0] = xres;
+ params[1] = xres >> 8;
+ params[2] = yres;
+ params[3] = yres >> 8;
+
+ channel_send (hw->channel, params, 4, &status);
+ status = expect_ack (hw);
+
+ return status;
+}
+
+/* set_scan_area()
+ *
+ * Sends the "set scan area" command to the scanner with the currently selected
+ * scan area. This scan area is already corrected for "color shuffling" if
+ * necessary.
+ */
+static SANE_Status
+set_scan_area (device *hw, int x, int y, int width, int height)
+{
+ SANE_Status status;
+ u_char params[8];
+
+ log_call ("(%d, %d, %d, %d)", x, y, width, height);
+
+ if (!hw->cmd->set_scan_area)
+ {
+ err_major ("set_scan_area not supported");
+ return SANE_STATUS_GOOD;
+ }
+
+ /* verify the scan area */
+ if (x < 0 || y < 0 || width <= 0 || height <= 0)
+ return SANE_STATUS_INVAL;
+
+ params[0] = ESC;
+ params[1] = hw->cmd->set_scan_area;
+
+ channel_send (hw->channel, params, 2, &status);
+ status = expect_ack (hw);
+ if (status != SANE_STATUS_GOOD)
+ return status;
+
+ params[0] = x;
+ params[1] = x >> 8;
+ params[2] = y;
+ params[3] = y >> 8;
+ params[4] = width;
+ params[5] = width >> 8;
+ params[6] = height;
+ params[7] = height >> 8;
+
+ channel_send (hw->channel, params, 8, &status);
+ status = expect_ack (hw);
+
+ return status;
+}
+
+/* set_color_correction_coefficients()
+ *
+ * Sends the "set color correction coefficients" command with the currently selected
+ * parameters to the scanner.
+ */
+
+static SANE_Status
+set_color_correction_coefficients (device *hw, Epson_Scanner *s)
+{
+ SANE_Status status;
+ u_char cmd = hw->cmd->set_color_correction_coefficients;
+ u_char params[2];
+ const int length = 9;
+ unsigned char cccoeff[9];
+
+ log_call ();
+
+ s->cct[0] = SANE_UNFIX (s->val[OPT_CCT_1].w);
+ s->cct[1] = SANE_UNFIX (s->val[OPT_CCT_2].w);
+ s->cct[2] = SANE_UNFIX (s->val[OPT_CCT_3].w);
+ s->cct[3] = SANE_UNFIX (s->val[OPT_CCT_4].w);
+ s->cct[4] = SANE_UNFIX (s->val[OPT_CCT_5].w);
+ s->cct[5] = SANE_UNFIX (s->val[OPT_CCT_6].w);
+ s->cct[6] = SANE_UNFIX (s->val[OPT_CCT_7].w);
+ s->cct[7] = SANE_UNFIX (s->val[OPT_CCT_8].w);
+ s->cct[8] = SANE_UNFIX (s->val[OPT_CCT_9].w);
+
+ if (!cmd) /* effect will be emulated */
+ return SANE_STATUS_GOOD;
+
+ params[0] = ESC;
+ params[1] = cmd;
+
+ channel_send (hw->channel, params, 2, &status);
+ if (SANE_STATUS_GOOD != (status = expect_ack (hw)))
+ return status;
+
+ get_colorcoeff_from_profile (s->cct, cccoeff);
+
+ log_data ("[ %d %d %d ][ %d %d %d ][ %d %d %d]",
+ cccoeff[0], cccoeff[1], cccoeff[2],
+ cccoeff[3], cccoeff[4], cccoeff[5],
+ cccoeff[6], cccoeff[7], cccoeff[8]);
+
+ channel_send (hw->channel, cccoeff, length, &status);
+ status = expect_ack (hw);
+
+ log_call ("exit");
+
+ return status;
+}
+
+
+static SANE_Status
+set_gamma_table (device *hw, const Epson_Scanner *s)
+{
+ SANE_Status status;
+ u_char cmd = hw->cmd->set_gamma_table;
+ u_char params[2];
+ const int length = 257;
+ u_char gamma[257];
+ int n;
+ int table;
+ static char gamma_cmds[] = { 'R', 'G', 'B' };
+
+ if (!cmd)
+ return SANE_STATUS_UNSUPPORTED;
+
+ log_call ();
+
+ params[0] = ESC;
+ params[1] = cmd;
+
+/* When handling inverted images, we must also invert the user
+ * supplied gamma function. This is *not* just 255-gamma -
+ * this gives a negative image.
+ */
+
+ if (strcmp_c ("GT-6600", hw->fw_name) == 0 ||
+ strcmp_c ("Perfection 610", hw->fw_name) == 0)
+ {
+ gamma_cmds[0] = 'R';
+ gamma_cmds[1] = 'B';
+ gamma_cmds[2] = 'G';
+ }
+
+ for (table = 0; table < 3; table++)
+ {
+ gamma[0] = gamma_cmds[table];
+ if (s->invert_image)
+ {
+ for (n = 0; n < 256; ++n)
+ {
+ gamma[n + 1] = 255 - s->gamma_table[table][255 - n];
+ }
+ }
+ else
+ {
+ for (n = 0; n < 256; ++n)
+ {
+ gamma[n + 1] = s->gamma_table[table][n];
+ }
+ }
+
+ channel_send (hw->channel, params, 2, &status);
+ if (SANE_STATUS_GOOD != (status = expect_ack (hw)))
+ return status;
+
+ channel_send (hw->channel, gamma, length, &status);
+ if (SANE_STATUS_GOOD != (status = expect_ack (hw)))
+ return status;
+ }
+
+ log_call ("exit");
+
+ return status;
+}
+
+static SANE_Status
+check_warmup (device *hw)
+{
+ SANE_Status status = check_ext_status (hw);
+
+ log_call ();
+
+ if (status == SANE_STATUS_DEVICE_BUSY)
+ {
+ int timeout;
+
+ for (timeout = 0; timeout < 60; timeout++)
+ {
+ status = check_ext_status (hw);
+
+ if (status == SANE_STATUS_DEVICE_BUSY)
+ sleep (1);
+ else
+ {
+ return status;
+ }
+ }
+ }
+ else
+ return status;
+
+ return status;
+}
+
+
+static SANE_Status
+check_ext_status (device *hw)
+{
+ SANE_Status status;
+
+ log_call ();
+ require (hw);
+
+ status = dev_request_extended_status (hw);
+
+ if (EXT_STATUS_WU & hw->ext_status)
+ {
+ log_info ("option: warming up");
+ status = SANE_STATUS_DEVICE_BUSY;
+ }
+
+ if (EXT_STATUS_FER & hw->ext_status)
+ {
+ log_info ("option: fatal error");
+ status = SANE_STATUS_INVAL;
+ }
+
+ if (hw->adf)
+ {
+ if (ADF_STATUS_ERR & hw->adf->status
+ || ADF_EXT_STATUS_ERR & hw->adf->ext_status)
+ {
+ log_info ("ADF: other error");
+ status = SANE_STATUS_INVAL;
+ }
+
+ if (ADF_STATUS_PE & hw->adf->status)
+ {
+ log_info ("ADF: no paper");
+ status = SANE_STATUS_NO_DOCS;
+ }
+
+ if (ADF_STATUS_PJ & hw->adf->status)
+ {
+ log_info ("ADF: paper jam");
+ status = SANE_STATUS_JAMMED;
+ }
+
+ if (ADF_STATUS_OPN & hw->adf->status)
+ {
+ log_info ("ADF: cover open");
+ status = SANE_STATUS_COVER_OPEN;
+ }
+
+ if (ADF_EXT_STATUS_DFE & hw->adf->ext_status)
+ {
+ log_info ("ADF: multi sheet feed");
+ status = SANE_STATUS_JAMMED;
+ }
+
+ if (ADF_EXT_STATUS_TR_OPN & hw->adf->ext_status)
+ {
+ log_info ("ADF: tray open");
+ status = SANE_STATUS_COVER_OPEN;
+ }
+ }
+
+ if (hw->tpu)
+ {
+ if (TPU_STATUS_ERR & hw->tpu->status)
+ {
+ log_info ("TPU: other error");
+ status = SANE_STATUS_INVAL;
+ }
+ }
+
+ if (hw->fbf)
+ {
+ /* ??? is this for MFDs? */
+ if (DV3_STATUS_OPN & hw->fbf->status)
+ {
+ log_info ("UNIT: Scanner Unit open");
+ status = SANE_STATUS_COVER_OPEN;
+ }
+ }
+
+ return status;
+}
+
+
+static SANE_Status
+initialize (device *hw)
+{
+ SANE_Status status;
+ u_char param[2];
+
+ log_call ();
+
+ if (!hw->cmd->initialize_scanner)
+ return SANE_STATUS_GOOD;
+
+ param[0] = ESC;
+ param[1] = hw->cmd->initialize_scanner;
+
+ channel_send (hw->channel, param, 2, &status);
+ status = expect_ack (hw);
+
+ return status;
+}
+
+
+static Epson_Scanner *first_handle = NULL;
+
+
+static EpsonHdr
+command (device *hw, const u_char * cmd, size_t cmd_size,
+ SANE_Status * status)
+{
+ EpsonHdr head;
+ u_char *buf;
+ int count;
+
+ log_call ();
+
+ if (NULL == (head = t_malloc (1,EpsonHdrRec)))
+ {
+ err_fatal ("%s", strerror (errno));
+ *status = SANE_STATUS_NO_MEM;
+ return (EpsonHdr) 0;
+ }
+
+ channel_send (hw->channel, cmd, cmd_size, status);
+
+ if (SANE_STATUS_GOOD != *status)
+ {
+ /* this is necessary for the GT-8000. I don't know why, but
+ it seems to fix the problem. It should not have any
+ ill effects on other scanners. */
+ *status = SANE_STATUS_GOOD;
+ channel_send (hw->channel, cmd, cmd_size, status);
+ if (SANE_STATUS_GOOD != *status)
+ return (EpsonHdr) 0;
+ }
+
+ buf = (u_char *) head;
+ buf += channel_recv (hw->channel, buf, 4, status);
+
+ if (SANE_STATUS_GOOD != *status)
+ {
+ delete (head);
+ return (EpsonHdr) 0;
+ }
+
+ switch (head->code)
+ {
+
+ case NAK:
+ /* fall through */
+ /* !!! is this really sufficient to report an error ? */
+ case ACK:
+ break; /* no need to read any more data after ACK or NAK */
+
+ case STX:
+ hw->status = head->status;
+
+ if (SANE_STATUS_GOOD != *status)
+ {
+ delete (head);
+ return (EpsonHdr) 0;
+ }
+
+ count = head->count2 * 255 + head->count1;
+ log_info ("need to read %d data bytes", count);
+
+ /* Grow head so it has enough space to hold an additional count
+ * bytes of payload. We can _not_ use t_realloc here.
+ */
+ if (NULL == (head = realloc (head, sizeof (EpsonHdrRec) + count)))
+ {
+ err_fatal ("%s", strerror (errno));
+ *status = SANE_STATUS_NO_MEM;
+ return (EpsonHdr) 0;
+ }
+
+ buf = head->buf;
+ channel_recv (hw->channel, buf, count, status);
+
+ if (SANE_STATUS_GOOD != *status)
+ {
+ delete (head);
+ return (EpsonHdr) 0;
+ }
+
+ break;
+
+ default:
+ if (0 == head->code)
+ { err_major ("Incompatible printer port (probably bi/directional)"); }
+ else if (cmd[cmd_size - 1] == head->code)
+ { err_major ("Incompatible printer port (probably not bi/directional)"); }
+
+ err_major ("Illegal response of scanner for command: %02x", head->code);
+ break;
+ }
+
+ return head;
+}
+
+
+static SANE_Status
+create_epson_device (device **devp, channel* ch)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+ device *dev = NULL;
+
+ require (devp && ch);
+
+ dev = t_calloc (1, device);
+ if (!dev)
+ {
+ err_fatal ("%s", strerror (errno));
+ return SANE_STATUS_NO_MEM;
+ }
+
+ dev->channel = ch;
+
+ init_resolution_info (&dev->res , NULL);
+ init_resolution_info (&dev->res_x, NULL);
+ init_resolution_info (&dev->res_y, NULL);
+
+ dev->cmd = &epson_cmd[EPSON_LEVEL_DEFAULT];
+
+ dev->fw_name = get_fw_name (dev->channel);
+
+ {
+ void *cfg = cfg_init (NULL, NULL);
+ dev->using_fs = !cfg_has_value (cfg, CFG_KEY_FS_BLACKLIST, dev->fw_name);
+ }
+
+ if (!dev->using_fs && CHAN_NET == ch->type)
+ {
+ err_fatal ("Network channel does not support scanning via ESC commands");
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ {
+ size_t protocol_max = (dev->using_fs ? UINT32_MAX : UINT16_MAX);
+
+ ch->set_max_request_size (ch, min (protocol_max, 512 * 1024));
+ }
+
+ initialize (dev);
+
+ if (dev->cmd->request_identity != 0)
+ {
+ status = get_identity_information (dev);
+ if (status != SANE_STATUS_GOOD)
+ {
+ return status;
+ }
+ }
+
+ if (dev->cmd->request_identity2 != 0)
+ {
+ get_hardware_property (dev);
+ if (status != SANE_STATUS_GOOD)
+ {
+ return status;
+ }
+ }
+
+ /* Check for the max. supported color depth and assign the values to
+ * the bitDepthList. Note that bitDepthList is a SANE word list and
+ * therefore has an initial element that indicates the number of its
+ * elements. We store up to 2 bit depths (8 and only one of 16, 14,
+ * and 12).
+ */
+
+ bitDepthList = t_malloc (1 + 2, SANE_Word);
+ if (bitDepthList == NULL)
+ {
+ err_fatal ("%s", strerror (errno));
+ return SANE_STATUS_NO_MEM;
+ }
+
+ bitDepthList[0] = 1; /* we start with one element in the list */
+ bitDepthList[1] = 8; /* 8bit is the default */
+
+ if (set_data_format (dev, 16) == SANE_STATUS_GOOD)
+ {
+ dev->maxDepth = 16;
+
+ bitDepthList[0]++;
+ bitDepthList[bitDepthList[0]] = 16;
+ }
+ else if (set_data_format (dev, 14) == SANE_STATUS_GOOD)
+ {
+ dev->maxDepth = 14;
+
+ bitDepthList[0]++;
+ bitDepthList[bitDepthList[0]] = 14;
+ }
+ else if (set_data_format (dev, 12) == SANE_STATUS_GOOD)
+ {
+ dev->maxDepth = 12;
+
+ bitDepthList[0]++;
+ bitDepthList[bitDepthList[0]] = 12;
+ }
+ else
+ {
+ dev->maxDepth = 8;
+ /* the default depth is already in the list */
+ }
+
+ log_info ("maximum color depth = %d", dev->maxDepth);
+
+ status = dev_request_extended_status (dev);
+ if (SANE_STATUS_GOOD != status)
+ {
+ if (SANE_STATUS_UNSUPPORTED != status)
+ {
+ err_minor ("failure processing extended status request");
+ }
+ }
+ else
+ {
+ const void *info = model_info_cache_get_info (dev->fw_name, &status);
+ if (SANE_STATUS_GOOD == status && info)
+ {
+ dev->scan_hard = model_info_get_profile (info);
+ model_info_customise_commands (info, dev->cmd);
+ dev->uses_locking = model_info_has_lock_commands (info);
+ }
+ else
+ {
+ err_minor ("failure getting model info (%s)", sane_strstatus (status));
+ }
+ }
+
+ if (0 == strcmp_c ("GT-8200", dev->fw_name))
+ {
+ /* Version 1.08 of the firmware is said to only report half of the
+ vertical size of the scan area. We should double that if it is
+ smaller than the horizontal scan area. Although we already try
+ doing this by fixing up the extended status reply, that doesn't
+ do the whole trick because we are dealing with a device type 0
+ scanner. In that case we get the FBF scan area via the 'ESC I'
+ command which does not include model info so we can not safely
+ fix up that reply.
+ */
+ if (dev->fbf && (dev->fbf->max_y < dev->fbf->max_x))
+ {
+ err_minor ("Fixing up buggy FBF max scan dimensions.");
+ dev->fbf->max_y *= 2;
+ update_ranges (dev, dev->fbf);
+ dev->need_color_reorder = SANE_TRUE;
+ }
+ }
+
+ if (dev->fbf)
+ {
+ log_info (" FBF: TL (%.2f, %.2f) -- BR (%.2f, %.2f) [in mm]",
+ SANE_UNFIX (dev->fbf->x_range.min),
+ SANE_UNFIX (dev->fbf->y_range.min),
+ SANE_UNFIX (dev->fbf->x_range.max),
+ SANE_UNFIX (dev->fbf->y_range.max));
+
+ // check for auto size detection support and cache the results
+ dev->fbf->has_size_check = ((0 != dev->fbf->doc_x)
+ && (0 != dev->fbf->doc_y));
+ }
+ if (dev->adf)
+ {
+ log_info (" ADF: TL (%.2f, %.2f) -- BR (%.2f, %.2f) [in mm]",
+ SANE_UNFIX (dev->adf->x_range.min),
+ SANE_UNFIX (dev->adf->y_range.min),
+ SANE_UNFIX (dev->adf->x_range.max),
+ SANE_UNFIX (dev->adf->y_range.max));
+ log_info (" ADF: %s, %s feed type",
+ (EXT_STATUS_ADFS & dev->ext_status ? "duplex" : "simplex"),
+ (EXT_STATUS_ADFT & dev->ext_status ? "page" : "sheet"));
+
+ // check for auto size detection support and cache the results
+ dev->adf->has_size_check = ((0 != dev->adf->doc_x)
+ && (0 != dev->adf->doc_y));
+
+ // handle the special case where some scanners claim to have auto
+ // document size detection support for flatbed but not for adf
+ if (dev->fbf && (dev->fbf->has_size_check && !dev->adf->has_size_check))
+ {
+ cmd_control_option_unit (dev, 0x01); // turn on ADF
+ dev_request_extended_status (dev); // now re-query scanner
+ // re-check for document size detection support
+ dev->adf->has_size_check = ((0 != dev->adf->doc_x)
+ && (0 != dev->adf->doc_y));
+ cmd_control_option_unit (dev, 0x00); // turn off ADF
+ }
+ }
+ if (dev->tpu)
+ {
+ log_info (" TPU: TL (%.2f, %.2f) -- BR (%.2f, %.2f) [in mm]",
+ SANE_UNFIX (dev->tpu->x_range.min),
+ SANE_UNFIX (dev->tpu->y_range.min),
+ SANE_UNFIX (dev->tpu->x_range.max),
+ SANE_UNFIX (dev->tpu->y_range.max));
+
+ // check for auto size detection support and cache the results
+ dev->tpu->has_size_check = ((0 != dev->tpu->doc_x)
+ && (0 != dev->tpu->doc_y));
+ }
+
+
+ /* establish defaults */
+ dev->need_reset_on_source_change = SANE_FALSE;
+
+ if (strcmp_c ("ES-9000H", dev->fw_name) == 0 ||
+ strcmp_c ("GT-30000", dev->fw_name) == 0)
+ {
+ dev->cmd->set_focus_position = 0;
+ dev->cmd->feed = 0x19;
+ }
+ else if (strcmp_c ("GT-8200", dev->fw_name) == 0 ||
+ strcmp_c ("Perfection1640", dev->fw_name) == 0 ||
+ strcmp_c ("GT-8700", dev->fw_name) == 0)
+ {
+ dev->cmd->feed = 0;
+ dev->cmd->set_focus_position = 0;
+ dev->need_reset_on_source_change = SANE_TRUE;
+ }
+
+ { /* set up supported scan sources */
+ int i = 0;
+ if (dev->fbf) dev->sources[i++] = FBF_STR;
+ if (dev->adf) dev->sources[i++] = ADF_STR;
+ if (dev->tpu) dev->sources[i++] = TPU_STR;
+ dev->sources[i] = NULL; /* terminator */
+ }
+
+ if (!dev->src) dev->src = (extension *) dev->fbf;
+ if (!dev->src) dev->src = (extension *) dev->adf;
+ if (!dev->src) dev->src = (extension *) dev->tpu;
+
+ require (dev->src);
+
+ if (!using (dev, fbf))
+ dev_set_option_unit (dev, false);
+
+ dev->polling_time = DEFAULT_POLLING_TIME;
+ if (push_button_needs_polling (dev)
+ || maintenance_is_supported (dev))
+ {
+ dev->polling_time = (250 * 1000);
+ }
+
+ *devp = dev;
+
+ return SANE_STATUS_GOOD;
+} /* create epson device */
+
+
+static SANE_Status
+create_epson_scanner (device *dev, Epson_Scanner **scanner)
+{
+ Epson_Scanner *s = t_calloc (1, Epson_Scanner);
+
+ if (!s)
+ {
+ err_fatal ("%s", strerror (errno));
+ return SANE_STATUS_NO_MEM;
+ }
+
+ s->hw = dev;
+ s->src = &s->raw;
+
+ if (s->hw->cmd->request_identity2 != 0)
+ {
+ /* reset constraints because we are pointing to different lists now
+ FIXME: Only needs to be done the first time we (successfully)
+ send the ESC i command. This should be done when constructing
+ the device and is therefore done by the time we construct a
+ scanner. While the content and size of the lists may vary
+ depending on the selected option, the list nature is constant.
+ Hmm, we may actually be zapping the lists ...
+ */
+ s->opt[OPT_X_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+ s->opt[OPT_X_RESOLUTION].constraint.word_list = s->hw->res_x.list;
+ s->opt[OPT_Y_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+ s->opt[OPT_Y_RESOLUTION].constraint.word_list = s->hw->res_y.list;
+ }
+
+ s->frame_count = 0;
+
+ promise (s->hw);
+ promise (s->src);
+
+ *scanner = s;
+ return SANE_STATUS_GOOD;
+}
+
+
+/*! \todo Release resources in error recovery.
+ */
+static SANE_Status
+create_sane_handle (Epson_Scanner **handle, const char *name, const void *dip)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+ device *dev = NULL;
+ Epson_Scanner *s = NULL;
+ channel *ch = NULL;
+
+ dev = NULL;
+
+ ch = channel_create (name, &status);
+ if (SANE_STATUS_GOOD != status) return status;
+
+ ch->open (ch, &status);
+ if (SANE_STATUS_GOOD != status) return status;
+
+ status = create_epson_device (&dev, ch);
+ if (SANE_STATUS_GOOD != status)
+ return status;
+
+ status = create_epson_scanner (dev, &s);
+ if (!s || SANE_STATUS_GOOD != status)
+ return status;
+
+ s->dip = dip;
+ init_options (s);
+
+ *handle = s;
+ return SANE_STATUS_GOOD;
+}
+
+static SANE_Status
+init_options (Epson_Scanner * s)
+{
+ int i;
+ SANE_Bool dummy;
+ SANE_Bool reload;
+
+ log_call ();
+
+ for (i = 0; i < NUM_OPTIONS; ++i)
+ {
+ s->opt[i].size = sizeof (SANE_Word);
+ s->opt[i].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
+ }
+
+ s->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS;
+ s->opt[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS;
+ s->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT;
+ s->val[OPT_NUM_OPTS].w = NUM_OPTIONS;
+
+ /* "Scan Mode" group: */
+ s->opt[OPT_MODE_GROUP].title = SANE_I18N ("Scan Mode");
+ s->opt[OPT_MODE_GROUP].desc = "";
+ s->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_MODE_GROUP].cap = 0;
+
+ /* scan mode */
+ s->opt[OPT_MODE].name = SANE_NAME_SCAN_MODE;
+ s->opt[OPT_MODE].title = SANE_TITLE_SCAN_MODE;
+ s->opt[OPT_MODE].desc = SANE_DESC_SCAN_MODE;
+ s->opt[OPT_MODE].type = SANE_TYPE_STRING;
+ s->opt[OPT_MODE].size = max_string_size (mode_list);
+ s->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_MODE].constraint.string_list = mode_list;
+ s->val[OPT_MODE].w = 2; /* Color */
+
+ /* bit depth */
+ s->opt[OPT_BIT_DEPTH].name = SANE_NAME_BIT_DEPTH;
+ s->opt[OPT_BIT_DEPTH].title = SANE_TITLE_BIT_DEPTH;
+ s->opt[OPT_BIT_DEPTH].desc = SANE_DESC_BIT_DEPTH;
+ s->opt[OPT_BIT_DEPTH].type = SANE_TYPE_INT;
+ s->opt[OPT_BIT_DEPTH].unit = SANE_UNIT_NONE;
+ s->opt[OPT_BIT_DEPTH].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+ s->opt[OPT_BIT_DEPTH].constraint.word_list = bitDepthList;
+ s->opt[OPT_BIT_DEPTH].cap |= SANE_CAP_INACTIVE;
+ s->val[OPT_BIT_DEPTH].w = bitDepthList[1]; /* the first "real" element is the default */
+
+ if (bitDepthList[0] == 1) /* only one element in the list -> hide the option */
+ s->opt[OPT_BIT_DEPTH].cap |= SANE_CAP_INACTIVE;
+
+ /* halftone */
+ s->opt[OPT_HALFTONE].name = SANE_NAME_HALFTONE;
+ s->opt[OPT_HALFTONE].title = SANE_TITLE_HALFTONE;
+ s->opt[OPT_HALFTONE].desc = SANE_I18N ("Selects the halftone.");
+
+ s->opt[OPT_HALFTONE].type = SANE_TYPE_STRING;
+ s->opt[OPT_HALFTONE].size = max_string_size (halftone_list_7);
+ s->opt[OPT_HALFTONE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+
+ if (s->hw->level >= 7)
+ s->opt[OPT_HALFTONE].constraint.string_list = halftone_list_7;
+ else if (s->hw->level >= 4)
+ s->opt[OPT_HALFTONE].constraint.string_list = halftone_list_4;
+ else
+ s->opt[OPT_HALFTONE].constraint.string_list = halftone_list;
+
+ s->val[OPT_HALFTONE].w = 1; /* Halftone A */
+
+ if (!s->hw->cmd->set_halftoning)
+ {
+ s->opt[OPT_HALFTONE].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* dropout */
+ s->opt[OPT_DROPOUT].name = "dropout";
+ s->opt[OPT_DROPOUT].title = SANE_I18N ("Dropout");
+ s->opt[OPT_DROPOUT].desc = SANE_I18N ("Selects the dropout.");
+
+ s->opt[OPT_DROPOUT].type = SANE_TYPE_STRING;
+ s->opt[OPT_DROPOUT].size = max_string_size (dropout_list);
+ s->opt[OPT_DROPOUT].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_DROPOUT].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_DROPOUT].constraint.string_list = dropout_list;
+ s->val[OPT_DROPOUT].w = 0; /* None */
+
+ /* brightness algorithm selection */
+ s->opt[OPT_BRIGHTNESS_METHOD].name = "brightness-method";
+ s->opt[OPT_BRIGHTNESS_METHOD].title = SANE_I18N ("Brightness Method");
+ s->opt[OPT_BRIGHTNESS_METHOD].desc = SANE_I18N ("Selects a method to change the brightness of the acquired image.");
+ s->opt[OPT_BRIGHTNESS_METHOD].type = SANE_TYPE_STRING;
+ s->opt[OPT_BRIGHTNESS_METHOD].unit = SANE_UNIT_NONE;
+ s->opt[OPT_BRIGHTNESS_METHOD].size = max_string_size (brightness_method_list);
+ s->opt[OPT_BRIGHTNESS_METHOD].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_BRIGHTNESS_METHOD].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_BRIGHTNESS_METHOD].constraint.string_list = brightness_method_list; s->val[OPT_BRIGHTNESS_METHOD].w = 0; /* first supported */
+
+ if (!s->hw->cmd->set_bright) /* skip "hardware" */
+ ++s->opt[OPT_BRIGHTNESS_METHOD].constraint.string_list;
+
+ /* brightness */
+ s->opt[OPT_BRIGHTNESS].name = SANE_NAME_BRIGHTNESS;
+ s->opt[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS;
+ s->opt[OPT_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS;
+ s->opt[OPT_BRIGHTNESS].type = SANE_TYPE_INT;
+ s->opt[OPT_BRIGHTNESS].unit = SANE_UNIT_NONE;
+ s->opt[OPT_BRIGHTNESS].size = sizeof (SANE_Int);
+ s->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->val[OPT_BRIGHTNESS].w = 0; /* neutral */
+
+ if (s->hw->cmd->set_bright)
+ {
+ s->opt[OPT_BRIGHTNESS].constraint.range = &s->hw->cmd->bright_range;
+ }
+ else
+ {
+ s->opt[OPT_BRIGHTNESS].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_BRIGHTNESS].constraint.range = &brightness_range;
+ }
+
+ /* contrast */
+ s->opt[OPT_CONTRAST].name = SANE_NAME_CONTRAST;
+ s->opt[OPT_CONTRAST].title = SANE_TITLE_CONTRAST;
+ s->opt[OPT_CONTRAST].desc = SANE_DESC_CONTRAST;
+ s->opt[OPT_CONTRAST].type = SANE_TYPE_INT;
+ s->opt[OPT_CONTRAST].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CONTRAST].size = sizeof (SANE_Int);
+ s->opt[OPT_CONTRAST].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_CONTRAST].constraint.range = &contrast_range;
+ s->val[OPT_CONTRAST].w = 0; /* neutral */
+
+ /* sharpness */
+ s->opt[OPT_SHARPNESS].name = "sharpness";
+ s->opt[OPT_SHARPNESS].title = SANE_I18N ("Sharpness");
+ s->opt[OPT_SHARPNESS].desc = "";
+
+ s->opt[OPT_SHARPNESS].type = SANE_TYPE_INT;
+ s->opt[OPT_SHARPNESS].unit = SANE_UNIT_NONE;
+ s->opt[OPT_SHARPNESS].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_SHARPNESS].constraint.range = &outline_emphasis_range;
+ s->val[OPT_SHARPNESS].w = 0; /* Normal */
+
+ if (!s->hw->cmd->set_outline_emphasis)
+ {
+ s->opt[OPT_SHARPNESS].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* gamma */
+ s->opt[OPT_GAMMA_CORRECTION].name = SANE_NAME_GAMMA_CORRECTION;
+ s->opt[OPT_GAMMA_CORRECTION].title = SANE_TITLE_GAMMA_CORRECTION;
+ s->opt[OPT_GAMMA_CORRECTION].desc = SANE_DESC_GAMMA_CORRECTION;
+
+ s->opt[OPT_GAMMA_CORRECTION].type = SANE_TYPE_STRING;
+ s->opt[OPT_GAMMA_CORRECTION].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ /*
+ * special handling for D1 function level - at this time I'm not
+ * testing for D1, I'm just assuming that all D level scanners will
+ * behave the same way. This has to be confirmed with the next D-level
+ * scanner
+ */
+ if (s->hw->cmd->level[0] == 'D')
+ {
+ s->opt[OPT_GAMMA_CORRECTION].size = max_string_size (gamma_list_d);
+ s->opt[OPT_GAMMA_CORRECTION].constraint.string_list = gamma_list_d;
+ s->val[OPT_GAMMA_CORRECTION].w = 1; /* Default */
+ s->hw->gamma_user_defined = gamma_userdefined_d;
+ s->hw->gamma_type = gamma_params_d;
+ }
+ else
+ {
+ s->opt[OPT_GAMMA_CORRECTION].size = max_string_size (gamma_list_ab);
+ s->opt[OPT_GAMMA_CORRECTION].constraint.string_list = gamma_list_ab;
+ s->val[OPT_GAMMA_CORRECTION].w = 0; /* Default */
+ s->hw->gamma_user_defined = gamma_userdefined_ab;
+ s->hw->gamma_type = gamma_params_ab;
+ }
+
+ if (!s->hw->cmd->set_gamma)
+ {
+ s->opt[OPT_GAMMA_CORRECTION].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* red gamma vector */
+ s->opt[OPT_GAMMA_VECTOR_R].name = SANE_NAME_GAMMA_VECTOR_R;
+ s->opt[OPT_GAMMA_VECTOR_R].title = SANE_TITLE_GAMMA_VECTOR_R;
+ s->opt[OPT_GAMMA_VECTOR_R].desc = SANE_DESC_GAMMA_VECTOR_R;
+
+ s->opt[OPT_GAMMA_VECTOR_R].type = SANE_TYPE_INT;
+ s->opt[OPT_GAMMA_VECTOR_R].unit = SANE_UNIT_NONE;
+ s->opt[OPT_GAMMA_VECTOR_R].size = 256 * sizeof (SANE_Word);
+ s->opt[OPT_GAMMA_VECTOR_R].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_GAMMA_VECTOR_R].constraint.range = &u8_range;
+ s->val[OPT_GAMMA_VECTOR_R].wa = &s->gamma_table[0][0];
+
+ /* green gamma vector */
+ s->opt[OPT_GAMMA_VECTOR_G].name = SANE_NAME_GAMMA_VECTOR_G;
+ s->opt[OPT_GAMMA_VECTOR_G].title = SANE_TITLE_GAMMA_VECTOR_G;
+ s->opt[OPT_GAMMA_VECTOR_G].desc = SANE_DESC_GAMMA_VECTOR_G;
+
+ s->opt[OPT_GAMMA_VECTOR_G].type = SANE_TYPE_INT;
+ s->opt[OPT_GAMMA_VECTOR_G].unit = SANE_UNIT_NONE;
+ s->opt[OPT_GAMMA_VECTOR_G].size = 256 * sizeof (SANE_Word);
+ s->opt[OPT_GAMMA_VECTOR_G].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_GAMMA_VECTOR_G].constraint.range = &u8_range;
+ s->val[OPT_GAMMA_VECTOR_G].wa = &s->gamma_table[1][0];
+
+ /* red gamma vector */
+ s->opt[OPT_GAMMA_VECTOR_B].name = SANE_NAME_GAMMA_VECTOR_B;
+ s->opt[OPT_GAMMA_VECTOR_B].title = SANE_TITLE_GAMMA_VECTOR_B;
+ s->opt[OPT_GAMMA_VECTOR_B].desc = SANE_DESC_GAMMA_VECTOR_B;
+
+ s->opt[OPT_GAMMA_VECTOR_B].type = SANE_TYPE_INT;
+ s->opt[OPT_GAMMA_VECTOR_B].unit = SANE_UNIT_NONE;
+ s->opt[OPT_GAMMA_VECTOR_B].size = 256 * sizeof (SANE_Word);
+ s->opt[OPT_GAMMA_VECTOR_B].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_GAMMA_VECTOR_B].constraint.range = &u8_range;
+ s->val[OPT_GAMMA_VECTOR_B].wa = &s->gamma_table[2][0];
+
+ if (s->hw->cmd->set_gamma_table &&
+ s->hw->gamma_user_defined[s->val[OPT_GAMMA_CORRECTION].w])
+ {
+ s->opt[OPT_GAMMA_VECTOR_R].cap &= ~SANE_CAP_INACTIVE;
+ s->opt[OPT_GAMMA_VECTOR_G].cap &= ~SANE_CAP_INACTIVE;
+ s->opt[OPT_GAMMA_VECTOR_B].cap &= ~SANE_CAP_INACTIVE;
+ }
+ else
+ {
+ s->opt[OPT_GAMMA_VECTOR_R].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_GAMMA_VECTOR_G].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_GAMMA_VECTOR_B].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* initialize the Gamma tables */
+ memset (&s->gamma_table[0], 0, 256 * sizeof (SANE_Word));
+ memset (&s->gamma_table[1], 0, 256 * sizeof (SANE_Word));
+ memset (&s->gamma_table[2], 0, 256 * sizeof (SANE_Word));
+ for (i = 0; i < 256; i++)
+ {
+ s->gamma_table[0][i] = i;
+ s->gamma_table[1][i] = i;
+ s->gamma_table[2][i] = i;
+ }
+
+ /* color correction */
+ s->opt[OPT_COLOR_CORRECTION].name = "color-correction";
+ s->opt[OPT_COLOR_CORRECTION].title = SANE_I18N ("Color correction");
+ s->opt[OPT_COLOR_CORRECTION].desc =
+ SANE_I18N
+ ("Sets the color correction table for the selected output device.");
+
+ s->opt[OPT_COLOR_CORRECTION].type = SANE_TYPE_STRING;
+ s->opt[OPT_COLOR_CORRECTION].size = 32;
+ s->opt[OPT_COLOR_CORRECTION].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_COLOR_CORRECTION].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ if (s->hw->cmd->level[0] == 'D')
+ {
+ s->opt[OPT_COLOR_CORRECTION].size = max_string_size (color_list_d);
+ s->opt[OPT_COLOR_CORRECTION].constraint.string_list = color_list_d;
+ s->val[OPT_COLOR_CORRECTION].w = 0; /* Default */
+ s->hw->color_user_defined = color_userdefined_d;
+ s->hw->color_type = color_params_d;
+ }
+ else
+ {
+ s->opt[OPT_COLOR_CORRECTION].size = max_string_size (color_list_ab);
+ s->opt[OPT_COLOR_CORRECTION].constraint.string_list = color_list_ab;
+ s->val[OPT_COLOR_CORRECTION].w = 5; /* scanner default: CRT monitors */
+ s->hw->color_user_defined = color_userdefined_ab;
+ s->hw->color_type = color_params_ab;
+ }
+
+ if (!s->hw->cmd->set_color_correction)
+ {
+ s->opt[OPT_COLOR_CORRECTION].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* resolution */
+ s->opt[OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION;
+ s->opt[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION;
+ s->opt[OPT_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION;
+ s->opt[OPT_RESOLUTION].type = SANE_TYPE_INT;
+ s->opt[OPT_RESOLUTION].unit = SANE_UNIT_DPI;
+
+ if (s->hw->cmd->level[0] != 'D')
+ {
+ s->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_RESOLUTION].constraint.range = &s->hw->dpi_range;
+ }
+ else
+ {
+ s->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+ s->opt[OPT_RESOLUTION].constraint.word_list = s->hw->res.list;
+ }
+ handle_resolution (s, OPT_RESOLUTION, DEFAULT_RESOLUTION);
+
+#undef SANE_NAME_SCAN_X_RESOLUTION
+#define SANE_NAME_SCAN_X_RESOLUTION "x-resolution"
+
+ /* resolution in main scan direction */
+ s->opt[OPT_X_RESOLUTION].name = SANE_NAME_SCAN_X_RESOLUTION;
+ s->opt[OPT_X_RESOLUTION].title = SANE_TITLE_SCAN_X_RESOLUTION;
+ s->opt[OPT_X_RESOLUTION].desc = SANE_DESC_SCAN_X_RESOLUTION;
+ s->opt[OPT_X_RESOLUTION].type = SANE_TYPE_INT;
+ s->opt[OPT_X_RESOLUTION].unit = SANE_UNIT_DPI;
+
+ if (s->hw->cmd->level[0] != 'D')
+ {
+ s->opt[OPT_X_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_X_RESOLUTION].constraint.range = &s->hw->dpi_range;
+ }
+ else
+ {
+ s->opt[OPT_X_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+ s->opt[OPT_X_RESOLUTION].constraint.word_list = s->hw->res_x.list;
+ s->opt[OPT_X_RESOLUTION].cap |= SANE_CAP_ADVANCED;
+ }
+ handle_resolution (s, OPT_X_RESOLUTION, DEFAULT_X_RESOLUTION);
+
+ /* resolution in sub scan direction */
+ s->opt[OPT_Y_RESOLUTION].name = SANE_NAME_SCAN_Y_RESOLUTION;
+ s->opt[OPT_Y_RESOLUTION].title = SANE_TITLE_SCAN_Y_RESOLUTION;
+ s->opt[OPT_Y_RESOLUTION].desc = SANE_DESC_SCAN_Y_RESOLUTION;
+ s->opt[OPT_Y_RESOLUTION].type = SANE_TYPE_INT;
+ s->opt[OPT_Y_RESOLUTION].unit = SANE_UNIT_DPI;
+
+ if (s->hw->cmd->level[0] != 'D')
+ {
+ s->opt[OPT_Y_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_Y_RESOLUTION].constraint.range = &s->hw->dpi_range;
+ }
+ else
+ {
+ s->opt[OPT_Y_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+ s->opt[OPT_Y_RESOLUTION].constraint.word_list = s->hw->res_y.list;
+ s->opt[OPT_Y_RESOLUTION].cap |= SANE_CAP_ADVANCED;
+ }
+ handle_resolution (s, OPT_Y_RESOLUTION, DEFAULT_Y_RESOLUTION);
+
+ switch (s->hw->productID)
+ {
+ /* We already use kludges for a number of models and don't want to
+ be burdened with rechecking their functionality. Really fixing
+ their support requires more changes than merely adding main/sub
+ resolution lists.
+ */
+ case 0x0116:
+ case 0x0118:
+ case 0x0119:
+ case 0x012b:
+ s->opt[OPT_X_RESOLUTION].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_Y_RESOLUTION].cap |= SANE_CAP_INACTIVE;
+ break;
+ default:
+ /* use the default capability set at the top of this function */
+ ;
+ }
+
+ /* threshold */
+ s->opt[OPT_THRESHOLD].name = SANE_NAME_THRESHOLD;
+ s->opt[OPT_THRESHOLD].title = SANE_TITLE_THRESHOLD;
+ s->opt[OPT_THRESHOLD].desc = SANE_DESC_THRESHOLD;
+
+ s->opt[OPT_THRESHOLD].type = SANE_TYPE_INT;
+ s->opt[OPT_THRESHOLD].unit = SANE_UNIT_NONE;
+ s->opt[OPT_THRESHOLD].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_THRESHOLD].constraint.range = &u8_range;
+ s->val[OPT_THRESHOLD].w = 0x80;
+
+ if (!s->hw->cmd->set_threshold)
+ {
+ s->opt[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE;
+ }
+
+ s->opt[OPT_CCT_GROUP].title = SANE_I18N ("Color correction coefficients");
+ s->opt[OPT_CCT_GROUP].desc = SANE_I18N ("Matrix multiplication of RGB");
+ s->opt[OPT_CCT_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_CCT_GROUP].cap = SANE_CAP_ADVANCED;
+
+ /* color correction coefficients */
+ s->opt[OPT_CCT_1].name = "cct-1";
+ s->opt[OPT_CCT_2].name = "cct-2";
+ s->opt[OPT_CCT_3].name = "cct-3";
+ s->opt[OPT_CCT_4].name = "cct-4";
+ s->opt[OPT_CCT_5].name = "cct-5";
+ s->opt[OPT_CCT_6].name = "cct-6";
+ s->opt[OPT_CCT_7].name = "cct-7";
+ s->opt[OPT_CCT_8].name = "cct-8";
+ s->opt[OPT_CCT_9].name = "cct-9";
+
+ s->opt[OPT_CCT_1].title = SANE_I18N ("Red");
+ s->opt[OPT_CCT_2].title = SANE_I18N ("Shift green to red");
+ s->opt[OPT_CCT_3].title = SANE_I18N ("Shift blue to red");
+ s->opt[OPT_CCT_4].title = SANE_I18N ("Shift red to green");
+ s->opt[OPT_CCT_5].title = SANE_I18N ("Green");
+ s->opt[OPT_CCT_6].title = SANE_I18N ("Shift blue to green");
+ s->opt[OPT_CCT_7].title = SANE_I18N ("Shift red to blue");
+ s->opt[OPT_CCT_8].title = SANE_I18N ("Shift green to blue");
+ s->opt[OPT_CCT_9].title = SANE_I18N ("Blue");
+
+ s->opt[OPT_CCT_1].desc = SANE_I18N ("Controls red level");
+ s->opt[OPT_CCT_2].desc = SANE_I18N ("Adds to red based on green level");
+ s->opt[OPT_CCT_3].desc = SANE_I18N ("Adds to red based on blue level");
+ s->opt[OPT_CCT_4].desc = SANE_I18N ("Adds to green based on red level");
+ s->opt[OPT_CCT_5].desc = SANE_I18N ("Controls green level");
+ s->opt[OPT_CCT_6].desc = SANE_I18N ("Adds to green based on blue level");
+ s->opt[OPT_CCT_7].desc = SANE_I18N ("Adds to blue based on red level");
+ s->opt[OPT_CCT_8].desc = SANE_I18N ("Adds to blue based on green level");
+ s->opt[OPT_CCT_9].desc = SANE_I18N ("Control blue level");
+
+ s->opt[OPT_CCT_1].type = SANE_TYPE_FIXED;
+ s->opt[OPT_CCT_2].type = SANE_TYPE_FIXED;
+ s->opt[OPT_CCT_3].type = SANE_TYPE_FIXED;
+ s->opt[OPT_CCT_4].type = SANE_TYPE_FIXED;
+ s->opt[OPT_CCT_5].type = SANE_TYPE_FIXED;
+ s->opt[OPT_CCT_6].type = SANE_TYPE_FIXED;
+ s->opt[OPT_CCT_7].type = SANE_TYPE_FIXED;
+ s->opt[OPT_CCT_8].type = SANE_TYPE_FIXED;
+ s->opt[OPT_CCT_9].type = SANE_TYPE_FIXED;
+
+ s->opt[OPT_CCT_1].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_CCT_2].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_CCT_3].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_CCT_4].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_CCT_5].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_CCT_6].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_CCT_7].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_CCT_8].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_CCT_9].cap |= SANE_CAP_ADVANCED;
+
+ s->opt[OPT_CCT_1].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_CCT_2].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_CCT_3].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_CCT_4].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_CCT_5].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_CCT_6].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_CCT_7].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_CCT_8].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_CCT_9].cap |= SANE_CAP_INACTIVE;
+
+ if (!s->hw->cmd->set_color_correction_coefficients)
+ {
+ s->opt[OPT_CCT_1].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_CCT_2].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_CCT_3].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_CCT_4].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_CCT_5].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_CCT_6].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_CCT_7].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_CCT_8].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_CCT_9].cap |= SANE_CAP_EMULATED;
+ }
+
+ s->opt[OPT_CCT_1].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CCT_2].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CCT_3].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CCT_4].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CCT_5].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CCT_6].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CCT_7].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CCT_8].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CCT_9].unit = SANE_UNIT_NONE;
+
+ s->opt[OPT_CCT_1].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_CCT_2].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_CCT_3].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_CCT_4].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_CCT_5].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_CCT_6].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_CCT_7].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_CCT_8].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_CCT_9].constraint_type = SANE_CONSTRAINT_RANGE;
+
+ s->hw->matrix_range.min = SANE_FIX (-2.0);
+ s->hw->matrix_range.max = SANE_FIX (2.0);
+ s->hw->matrix_range.quant = 0;
+
+ s->opt[OPT_CCT_1].constraint.range = &s->hw->matrix_range;
+ s->opt[OPT_CCT_2].constraint.range = &s->hw->matrix_range;
+ s->opt[OPT_CCT_3].constraint.range = &s->hw->matrix_range;
+ s->opt[OPT_CCT_4].constraint.range = &s->hw->matrix_range;
+ s->opt[OPT_CCT_5].constraint.range = &s->hw->matrix_range;
+ s->opt[OPT_CCT_6].constraint.range = &s->hw->matrix_range;
+ s->opt[OPT_CCT_7].constraint.range = &s->hw->matrix_range;
+ s->opt[OPT_CCT_8].constraint.range = &s->hw->matrix_range;
+ s->opt[OPT_CCT_9].constraint.range = &s->hw->matrix_range;
+
+ s->val[OPT_CCT_1].w = SANE_FIX (1.0);
+ s->val[OPT_CCT_2].w = 0;
+ s->val[OPT_CCT_3].w = 0;
+ s->val[OPT_CCT_4].w = 0;
+ s->val[OPT_CCT_5].w = SANE_FIX (1.0);
+ s->val[OPT_CCT_6].w = 0;
+ s->val[OPT_CCT_7].w = 0;
+ s->val[OPT_CCT_8].w = 0;
+ s->val[OPT_CCT_9].w = SANE_FIX (1.0);
+
+ /* "Advanced" group: */
+ s->opt[OPT_ADVANCED_GROUP].title = SANE_I18N ("Advanced");
+ s->opt[OPT_ADVANCED_GROUP].desc = "";
+ s->opt[OPT_ADVANCED_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_ADVANCED_GROUP].cap = SANE_CAP_ADVANCED;
+
+ /* mirror */
+ s->opt[OPT_MIRROR].name = "mirror";
+ s->opt[OPT_MIRROR].title = SANE_I18N ("Mirror image");
+ s->opt[OPT_MIRROR].desc = SANE_I18N ("Mirror the image.");
+
+ s->opt[OPT_MIRROR].type = SANE_TYPE_BOOL;
+ s->val[OPT_MIRROR].w = SANE_FALSE;
+
+ if (!s->hw->cmd->mirror_image)
+ {
+ s->opt[OPT_MIRROR].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* speed */
+ s->opt[OPT_SPEED].name = SANE_NAME_SCAN_SPEED;
+ s->opt[OPT_SPEED].title = SANE_TITLE_SCAN_SPEED;
+ s->opt[OPT_SPEED].desc = SANE_DESC_SCAN_SPEED;
+
+ s->opt[OPT_SPEED].type = SANE_TYPE_BOOL;
+ s->val[OPT_SPEED].w = SANE_FALSE;
+
+ if (!s->hw->cmd->set_speed)
+ {
+ s->opt[OPT_SPEED].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* preview speed */
+ s->opt[OPT_PREVIEW_SPEED].name = "preview-speed";
+ s->opt[OPT_PREVIEW_SPEED].title = SANE_I18N ("Speed");
+ s->opt[OPT_PREVIEW_SPEED].desc = "";
+
+ s->opt[OPT_PREVIEW_SPEED].type = SANE_TYPE_BOOL;
+ s->val[OPT_PREVIEW_SPEED].w = SANE_FALSE;
+
+ if (!s->hw->cmd->set_speed)
+ {
+ s->opt[OPT_PREVIEW_SPEED].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* auto area segmentation */
+ s->opt[OPT_AAS].name = "auto-area-segmentation";
+ s->opt[OPT_AAS].title = SANE_I18N ("Auto area segmentation");
+ s->opt[OPT_AAS].desc = "";
+
+ s->opt[OPT_AAS].type = SANE_TYPE_BOOL;
+ s->val[OPT_AAS].w = SANE_TRUE;
+
+ if (!s->hw->cmd->control_auto_area_segmentation)
+ {
+ s->opt[OPT_AAS].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* limit resolution list */
+ s->opt[OPT_LIMIT_RESOLUTION].name = "short-resolution";
+ s->opt[OPT_LIMIT_RESOLUTION].title = SANE_I18N ("Short resolution list");
+ s->opt[OPT_LIMIT_RESOLUTION].desc =
+ SANE_I18N ("Display short resolution list");
+ s->opt[OPT_LIMIT_RESOLUTION].type = SANE_TYPE_BOOL;
+ s->val[OPT_LIMIT_RESOLUTION].w = SANE_FALSE;
+
+ if (SANE_CONSTRAINT_WORD_LIST != s->opt[OPT_RESOLUTION].constraint_type)
+ {
+ s->opt[OPT_LIMIT_RESOLUTION].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* zoom */
+ s->opt[OPT_ZOOM].name = "zoom";
+ s->opt[OPT_ZOOM].title = SANE_I18N ("Zoom");
+ s->opt[OPT_ZOOM].desc =
+ SANE_I18N ("Defines the zoom factor the scanner will use");
+
+ s->opt[OPT_ZOOM].type = SANE_TYPE_INT;
+ s->opt[OPT_ZOOM].unit = SANE_UNIT_NONE;
+ s->opt[OPT_ZOOM].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_ZOOM].constraint.range = &zoom_range;
+ s->val[OPT_ZOOM].w = 100;
+
+ if (s->hw->using_fs) s->hw->cmd->set_zoom = 0;
+
+ if (!s->hw->cmd->set_zoom)
+ {
+ s->opt[OPT_ZOOM].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* "Preview settings" group: */
+ s->opt[OPT_PREVIEW_GROUP].title = SANE_TITLE_PREVIEW;
+ s->opt[OPT_PREVIEW_GROUP].desc = "";
+ s->opt[OPT_PREVIEW_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_PREVIEW_GROUP].cap = SANE_CAP_ADVANCED;
+
+ /* preview */
+ s->opt[OPT_PREVIEW].name = SANE_NAME_PREVIEW;
+ s->opt[OPT_PREVIEW].title = SANE_TITLE_PREVIEW;
+ s->opt[OPT_PREVIEW].desc = SANE_DESC_PREVIEW;
+
+ s->opt[OPT_PREVIEW].type = SANE_TYPE_BOOL;
+ s->val[OPT_PREVIEW].w = SANE_FALSE;
+
+ /* "Geometry" group: */
+ s->opt[OPT_GEOMETRY_GROUP].title = SANE_I18N ("Geometry");
+ s->opt[OPT_GEOMETRY_GROUP].desc = "";
+ s->opt[OPT_GEOMETRY_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_GEOMETRY_GROUP].cap = SANE_CAP_ADVANCED;
+
+ /* media size oriented scan area setting */
+ s->opt[OPT_SCAN_AREA].name = "scan-area";
+ s->opt[OPT_SCAN_AREA].title = SANE_I18N ("Scan area");
+ s->opt[OPT_SCAN_AREA].desc =
+ SANE_I18N ("Select an area to scan based on well-known media sizes.");
+ s->opt[OPT_SCAN_AREA].type = SANE_TYPE_STRING;
+ s->opt[OPT_SCAN_AREA].size = 0;
+ s->opt[OPT_SCAN_AREA].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_SCAN_AREA].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_SCAN_AREA].constraint.string_list = NULL;
+ s->val[OPT_SCAN_AREA].w = 0;
+
+ /* Quick format */
+ s->opt[OPT_QUICK_FORMAT].name = "quick-format";
+ s->opt[OPT_QUICK_FORMAT].title = SANE_I18N ("Quick format");
+ s->opt[OPT_QUICK_FORMAT].desc =
+ SANE_I18N ("Select an area to scan based on well-known media sizes. (DEPRECATED)");
+ s->opt[OPT_QUICK_FORMAT].type = SANE_TYPE_STRING;
+ s->opt[OPT_QUICK_FORMAT].size = 0;
+ s->opt[OPT_QUICK_FORMAT].cap |= SANE_CAP_INACTIVE;
+ s->opt[OPT_QUICK_FORMAT].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_QUICK_FORMAT].constraint.string_list = NULL;
+ s->val[OPT_QUICK_FORMAT].w = 0;
+
+ handle_scan_area (s, 0); /* divines device/setting dependent bits */
+
+ /* top-left x */
+ s->opt[OPT_TL_X].name = SANE_NAME_SCAN_TL_X;
+ s->opt[OPT_TL_X].title = SANE_TITLE_SCAN_TL_X;
+ s->opt[OPT_TL_X].desc = SANE_DESC_SCAN_TL_X;
+
+ s->opt[OPT_TL_X].type = SANE_TYPE_FIXED;
+ s->opt[OPT_TL_X].unit = SANE_UNIT_MM;
+ s->opt[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_TL_X].constraint.range = &(s->hw->src->x_range);
+ s->val[OPT_TL_X].w = 0;
+
+ /* top-left y */
+ s->opt[OPT_TL_Y].name = SANE_NAME_SCAN_TL_Y;
+ s->opt[OPT_TL_Y].title = SANE_TITLE_SCAN_TL_Y;
+ s->opt[OPT_TL_Y].desc = SANE_DESC_SCAN_TL_Y;
+
+ s->opt[OPT_TL_Y].type = SANE_TYPE_FIXED;
+ s->opt[OPT_TL_Y].unit = SANE_UNIT_MM;
+ s->opt[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_TL_Y].constraint.range = &(s->hw->src->y_range);
+ s->val[OPT_TL_Y].w = 0;
+
+ /* bottom-right x */
+ s->opt[OPT_BR_X].name = SANE_NAME_SCAN_BR_X;
+ s->opt[OPT_BR_X].title = SANE_TITLE_SCAN_BR_X;
+ s->opt[OPT_BR_X].desc = SANE_DESC_SCAN_BR_X;
+
+ s->opt[OPT_BR_X].type = SANE_TYPE_FIXED;
+ s->opt[OPT_BR_X].unit = SANE_UNIT_MM;
+ s->opt[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_BR_X].constraint.range = &(s->hw->src->x_range);
+ s->val[OPT_BR_X].w = s->hw->src->x_range.max;
+
+ /* bottom-right y */
+ s->opt[OPT_BR_Y].name = SANE_NAME_SCAN_BR_Y;
+ s->opt[OPT_BR_Y].title = SANE_TITLE_SCAN_BR_Y;
+ s->opt[OPT_BR_Y].desc = SANE_DESC_SCAN_BR_Y;
+
+ s->opt[OPT_BR_Y].type = SANE_TYPE_FIXED;
+ s->opt[OPT_BR_Y].unit = SANE_UNIT_MM;
+ s->opt[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_BR_Y].constraint.range = &(s->hw->src->y_range);
+ s->val[OPT_BR_Y].w = s->hw->src->y_range.max;
+
+ /* "Optional equipment" group: */
+ s->opt[OPT_EQU_GROUP].title = SANE_I18N ("Optional equipment");
+ s->opt[OPT_EQU_GROUP].desc = "";
+ s->opt[OPT_EQU_GROUP].type = SANE_TYPE_GROUP;
+ s->opt[OPT_EQU_GROUP].cap = SANE_CAP_ADVANCED;
+
+ /* source */
+ s->opt[OPT_SOURCE].name = SANE_NAME_SCAN_SOURCE;
+ s->opt[OPT_SOURCE].title = SANE_TITLE_SCAN_SOURCE;
+ s->opt[OPT_SOURCE].desc = SANE_DESC_SCAN_SOURCE;
+
+ s->opt[OPT_SOURCE].type = SANE_TYPE_STRING;
+ s->opt[OPT_SOURCE].size = max_string_size (s->hw->sources);
+
+ s->opt[OPT_SOURCE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_SOURCE].constraint.string_list = s->hw->sources;
+
+ if (!s->hw->sources[1]) /* two or more scan sources */
+ {
+ s->opt[OPT_SOURCE].cap |= SANE_CAP_INACTIVE;
+ }
+ s->val[OPT_SOURCE].w = 0;
+
+
+ /* film type */
+ s->opt[OPT_FILM_TYPE].name = "film-type";
+ s->opt[OPT_FILM_TYPE].title = SANE_I18N ("Film type");
+ s->opt[OPT_FILM_TYPE].desc = "";
+
+ s->opt[OPT_FILM_TYPE].type = SANE_TYPE_STRING;
+ s->opt[OPT_FILM_TYPE].size = max_string_size (film_list);
+
+ s->opt[OPT_FILM_TYPE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_FILM_TYPE].constraint.string_list = film_list;
+
+ s->val[OPT_FILM_TYPE].w = 0;
+
+ deactivateOption (s, OPT_FILM_TYPE, &dummy); /* default is inactive */
+
+ /* focus position */
+ s->opt[OPT_FOCUS].name = SANE_EPSON_FOCUS_NAME;
+ s->opt[OPT_FOCUS].title = SANE_EPSON_FOCUS_TITLE;
+ s->opt[OPT_FOCUS].desc = SANE_EPSON_FOCUS_DESC;
+ s->opt[OPT_FOCUS].type = SANE_TYPE_STRING;
+ s->opt[OPT_FOCUS].size = max_string_size (focus_list);
+ s->opt[OPT_FOCUS].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_FOCUS].constraint.string_list = focus_list;
+ s->val[OPT_FOCUS].w = 0;
+
+ s->opt[OPT_FOCUS].cap |= SANE_CAP_ADVANCED;
+ if (s->hw->tpu && s->hw->tpu->has_focus)
+ {
+ s->opt[OPT_FOCUS].cap &= ~SANE_CAP_INACTIVE;
+ }
+ else
+ {
+ s->opt[OPT_FOCUS].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* forward feed / eject */
+ s->opt[OPT_EJECT].name = "eject";
+ s->opt[OPT_EJECT].title = SANE_I18N ("Eject");
+ s->opt[OPT_EJECT].desc = SANE_I18N ("Eject the sheet in the ADF");
+
+ s->opt[OPT_EJECT].type = SANE_TYPE_BUTTON;
+
+ if ((!s->hw->adf) && (!s->hw->cmd->set_bay))
+ { /* Hack: Using set_bay to indicate. */
+ s->opt[OPT_EJECT].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* auto forward feed / eject */
+ s->opt[OPT_AUTO_EJECT].name = "auto-eject";
+ s->opt[OPT_AUTO_EJECT].title = SANE_I18N ("Auto eject");
+ s->opt[OPT_AUTO_EJECT].desc = SANE_I18N ("Eject document after scanning");
+
+ s->opt[OPT_AUTO_EJECT].type = SANE_TYPE_BOOL;
+ s->val[OPT_AUTO_EJECT].w = SANE_TRUE;
+
+ if (s->hw->adf) s->hw->adf->auto_eject = SANE_TRUE;
+
+ if (!s->hw->adf)
+ {
+ s->opt[OPT_AUTO_EJECT].cap |= SANE_CAP_INACTIVE;
+ }
+
+ s->opt[OPT_ADF_MODE].name = "adf-mode";
+ s->opt[OPT_ADF_MODE].title = SANE_I18N ("ADF Mode");
+ s->opt[OPT_ADF_MODE].desc =
+ SANE_I18N ("Selects the ADF mode (simplex/duplex)");
+ s->opt[OPT_ADF_MODE].type = SANE_TYPE_STRING;
+ s->opt[OPT_ADF_MODE].size = max_string_size (adf_mode_list);
+ s->opt[OPT_ADF_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_ADF_MODE].constraint.string_list = adf_mode_list;
+ s->val[OPT_ADF_MODE].w = 0; /* simplex */
+
+ if (!(using (s->hw, adf) && (EXT_STATUS_ADFS & s->hw->ext_status)))
+ {
+ s->opt[OPT_ADF_MODE].cap |= SANE_CAP_INACTIVE;
+ }
+
+ /* select bay */
+ s->opt[OPT_BAY].name = "bay";
+ s->opt[OPT_BAY].title = SANE_I18N ("Bay");
+ s->opt[OPT_BAY].desc = SANE_I18N ("Select bay to scan");
+
+ s->opt[OPT_BAY].type = SANE_TYPE_STRING;
+ s->opt[OPT_BAY].size = max_string_size (bay_list);
+ s->opt[OPT_BAY].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_BAY].constraint.string_list = bay_list;
+ s->val[OPT_BAY].w = 0; /* Bay 1 */
+
+ if (!s->hw->cmd->set_bay)
+ {
+ s->opt[OPT_BAY].cap |= SANE_CAP_INACTIVE;
+ }
+
+ s->opt[OPT_WAIT_FOR_BUTTON].name = SANE_EPSON_WAIT_FOR_BUTTON_NAME;
+ s->opt[OPT_WAIT_FOR_BUTTON].title = SANE_EPSON_WAIT_FOR_BUTTON_TITLE;
+ s->opt[OPT_WAIT_FOR_BUTTON].desc = SANE_EPSON_WAIT_FOR_BUTTON_DESC;
+
+ s->opt[OPT_WAIT_FOR_BUTTON].type = SANE_TYPE_BOOL;
+ s->opt[OPT_WAIT_FOR_BUTTON].unit = SANE_UNIT_NONE;
+ s->opt[OPT_WAIT_FOR_BUTTON].constraint_type = SANE_CONSTRAINT_NONE;
+ s->opt[OPT_WAIT_FOR_BUTTON].constraint.range = NULL;
+ s->opt[OPT_WAIT_FOR_BUTTON].cap |= SANE_CAP_ADVANCED;
+ s->val[OPT_WAIT_FOR_BUTTON].w = SANE_FALSE;
+
+ if (!s->hw->cmd->request_push_button_status
+ || push_button_is_black_listed (s->hw))
+ {
+ s->opt[OPT_WAIT_FOR_BUTTON].cap |= SANE_CAP_INACTIVE;
+ }
+ else if (push_button_needs_polling (s->hw))
+ {
+ s->val[OPT_WAIT_FOR_BUTTON].w = SANE_TRUE;
+ }
+
+ s->opt[OPT_MONITOR_BUTTON].name = SANE_EPSON_MONITOR_BUTTON_NAME;
+ s->opt[OPT_MONITOR_BUTTON].title = SANE_EPSON_MONITOR_BUTTON_TITLE;
+ s->opt[OPT_MONITOR_BUTTON].desc = SANE_EPSON_MONITOR_BUTTON_DESC;
+
+ s->opt[OPT_MONITOR_BUTTON].type = SANE_TYPE_BOOL;
+ s->opt[OPT_MONITOR_BUTTON].unit = SANE_UNIT_NONE;
+ s->opt[OPT_MONITOR_BUTTON].constraint_type = SANE_CONSTRAINT_NONE;
+ s->opt[OPT_MONITOR_BUTTON].constraint.range = NULL;
+ s->opt[OPT_MONITOR_BUTTON].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_MONITOR_BUTTON].cap &= ~SANE_CAP_SOFT_SELECT;
+ s->val[OPT_MONITOR_BUTTON].w = SANE_FALSE;
+
+ if (!s->hw->cmd->request_push_button_status
+ || push_button_is_black_listed (s->hw))
+ {
+ s->opt[OPT_MONITOR_BUTTON].cap |= SANE_CAP_INACTIVE;
+ }
+
+ s->opt[OPT_POLLING_TIME].name = SANE_EPSON_POLLING_TIME_NAME;
+ s->opt[OPT_POLLING_TIME].title = SANE_EPSON_POLLING_TIME_TITLE;
+ s->opt[OPT_POLLING_TIME].desc = SANE_EPSON_POLLING_TIME_DESC;
+
+ s->opt[OPT_POLLING_TIME].type = SANE_TYPE_INT;
+ s->opt[OPT_POLLING_TIME].unit = SANE_UNIT_MICROSECOND;
+ s->opt[OPT_POLLING_TIME].constraint_type = SANE_CONSTRAINT_NONE;
+ s->opt[OPT_POLLING_TIME].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_POLLING_TIME].cap &= ~SANE_CAP_SOFT_SELECT;
+ s->val[OPT_POLLING_TIME].w = s->hw->polling_time;
+
+ if (!s->hw->cmd->request_push_button_status
+ || push_button_is_black_listed (s->hw))
+ {
+ s->opt[OPT_POLLING_TIME].cap |= SANE_CAP_INACTIVE;
+ }
+
+ s->opt[OPT_NEEDS_POLLING].name = SANE_EPSON_NEEDS_POLLING_NAME;
+ s->opt[OPT_NEEDS_POLLING].title = SANE_EPSON_NEEDS_POLLING_TITLE;
+ s->opt[OPT_NEEDS_POLLING].desc = SANE_EPSON_NEEDS_POLLING_DESC;
+
+ s->opt[OPT_NEEDS_POLLING].type = SANE_TYPE_BOOL;
+ s->opt[OPT_NEEDS_POLLING].unit = SANE_UNIT_NONE;
+ s->opt[OPT_NEEDS_POLLING].constraint_type = SANE_CONSTRAINT_NONE;
+ s->opt[OPT_NEEDS_POLLING].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_NEEDS_POLLING].cap &= ~SANE_CAP_SOFT_SELECT;
+ s->val[OPT_NEEDS_POLLING].w = SANE_FALSE;
+
+ if (!s->hw->cmd->request_push_button_status
+ || push_button_is_black_listed (s->hw))
+ {
+ s->opt[OPT_NEEDS_POLLING].cap |= SANE_CAP_INACTIVE;
+ }
+ else if (push_button_needs_polling (s->hw))
+ {
+ s->val[OPT_NEEDS_POLLING].w = SANE_TRUE;
+ }
+
+ s->opt[OPT_DETECT_DOC_SIZE].name = SANE_EPSON_DETECT_DOC_SIZE_NAME;
+ s->opt[OPT_DETECT_DOC_SIZE].title = SANE_EPSON_DETECT_DOC_SIZE_TITLE;
+ s->opt[OPT_DETECT_DOC_SIZE].desc = SANE_EPSON_DETECT_DOC_SIZE_DESC;
+
+ s->opt[OPT_DETECT_DOC_SIZE].type = SANE_TYPE_BOOL;
+ s->opt[OPT_DETECT_DOC_SIZE].unit = SANE_UNIT_NONE;
+ s->opt[OPT_DETECT_DOC_SIZE].constraint_type = SANE_CONSTRAINT_NONE;
+ s->opt[OPT_DETECT_DOC_SIZE].constraint.range = NULL;
+ s->opt[OPT_DETECT_DOC_SIZE].cap |= SANE_CAP_ADVANCED;
+ s->val[OPT_DETECT_DOC_SIZE].w = SANE_FALSE;
+
+ if (!has_size_check_support (s->hw->src))
+ {
+ s->opt[OPT_DETECT_DOC_SIZE].cap |= SANE_CAP_INACTIVE;
+ }
+
+ s->opt[OPT_SCAN_AREA_IS_VALID].name = SANE_EPSON_SCAN_AREA_IS_VALID_NAME;
+ s->opt[OPT_SCAN_AREA_IS_VALID].title = SANE_EPSON_SCAN_AREA_IS_VALID_TITLE;
+ s->opt[OPT_SCAN_AREA_IS_VALID].desc = SANE_EPSON_SCAN_AREA_IS_VALID_DESC;
+
+ s->opt[OPT_SCAN_AREA_IS_VALID].type = SANE_TYPE_BOOL;
+ s->opt[OPT_SCAN_AREA_IS_VALID].unit = SANE_UNIT_NONE;
+ s->opt[OPT_SCAN_AREA_IS_VALID].constraint_type = SANE_CONSTRAINT_NONE;
+ s->opt[OPT_SCAN_AREA_IS_VALID].constraint.range = NULL;
+ s->opt[OPT_SCAN_AREA_IS_VALID].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
+ s->val[OPT_SCAN_AREA_IS_VALID].w = SANE_FALSE;
+
+ s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].name =
+ SANE_EPSON_ADF_DUPLEX_DIRECTION_MATCHES_NAME;
+ s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].title =
+ SANE_EPSON_ADF_DUPLEX_DIRECTION_MATCHES_TITLE;
+ s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].desc =
+ SANE_EPSON_ADF_DUPLEX_DIRECTION_MATCHES_DESC;
+
+ s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].type = SANE_TYPE_BOOL;
+ s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].unit = SANE_UNIT_NONE;
+ s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].constraint_type =
+ SANE_CONSTRAINT_NONE;
+ s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].constraint.range = NULL;
+ s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].cap =
+ SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
+ s->val[OPT_ADF_DUPLEX_DIRECTION_MATCHES].w = SANE_FALSE;
+
+ if (!(using (s->hw, adf) && (EXT_STATUS_ADFS & s->hw->ext_status)))
+ {
+ s->opt[OPT_ADF_DUPLEX_DIRECTION_MATCHES].cap |= SANE_CAP_INACTIVE;
+ }
+
+ s->opt[OPT_ADF_AUTO_SCAN].name = SANE_EPSON_ADF_AUTO_SCAN_NAME;
+ s->opt[OPT_ADF_AUTO_SCAN].title = SANE_EPSON_ADF_AUTO_SCAN_TITLE;
+ s->opt[OPT_ADF_AUTO_SCAN].desc = SANE_EPSON_ADF_AUTO_SCAN_DESC;
+
+ s->opt[OPT_ADF_AUTO_SCAN].type = SANE_TYPE_BOOL;
+ s->opt[OPT_ADF_AUTO_SCAN].unit = SANE_UNIT_NONE;
+ s->opt[OPT_ADF_AUTO_SCAN].constraint_type = SANE_CONSTRAINT_NONE;
+ s->opt[OPT_ADF_AUTO_SCAN].constraint.range = NULL;
+ s->opt[OPT_ADF_AUTO_SCAN].cap |= SANE_CAP_ADVANCED;
+ s->val[OPT_ADF_AUTO_SCAN].w = SANE_FALSE;
+
+ if (!using (s->hw, adf) || !(FSI_CAP_ADFAS & s->hw->fsi_cap_2))
+ {
+ s->opt[OPT_ADF_AUTO_SCAN].cap |= SANE_CAP_INACTIVE;
+ }
+
+ s->opt[OPT_ADF_DFD_SENSITIVITY].name = SANE_EPSON_ADF_DFD_SENSITIVITY_NAME;
+ s->opt[OPT_ADF_DFD_SENSITIVITY].title = SANE_EPSON_ADF_DFD_SENSITIVITY_TITLE;
+ s->opt[OPT_ADF_DFD_SENSITIVITY].desc = SANE_EPSON_ADF_DFD_SENSITIVITY_DESC;
+
+ s->opt[OPT_ADF_DFD_SENSITIVITY].type = SANE_TYPE_STRING;
+ s->opt[OPT_ADF_DFD_SENSITIVITY].unit = SANE_UNIT_NONE;
+ s->opt[OPT_ADF_DFD_SENSITIVITY].size = max_string_size (dfd_sensitivity);
+ s->opt[OPT_ADF_DFD_SENSITIVITY].constraint_type = SANE_CONSTRAINT_STRING_LIST;
+ s->opt[OPT_ADF_DFD_SENSITIVITY].constraint.string_list = dfd_sensitivity;
+ s->opt[OPT_ADF_DFD_SENSITIVITY].cap |= SANE_CAP_ADVANCED;
+ s->val[OPT_ADF_DFD_SENSITIVITY].w = 0;
+
+ if (!using (s->hw, adf) || !(FSI_CAP_DFD & s->hw->fsi_cap_2))
+ {
+ s->opt[OPT_ADF_DFD_SENSITIVITY].cap |= SANE_CAP_INACTIVE;
+ }
+
+ s->opt[OPT_EXT_SANE_STATUS].name = "ext-sane-status";
+ s->opt[OPT_EXT_SANE_STATUS].title = "Extended SANE Status";
+ s->opt[OPT_EXT_SANE_STATUS].desc = "Ugly kludge to provide additional status message strings to a frontend.";
+
+ s->opt[OPT_EXT_SANE_STATUS].type = SANE_TYPE_INT;
+ s->opt[OPT_EXT_SANE_STATUS].unit = SANE_UNIT_NONE;
+ s->opt[OPT_EXT_SANE_STATUS].constraint_type = SANE_CONSTRAINT_RANGE;
+ s->opt[OPT_EXT_SANE_STATUS].constraint.range = &ext_sane_status;
+ s->opt[OPT_EXT_SANE_STATUS].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
+ s->val[OPT_EXT_SANE_STATUS].w = 0;
+
+ /* deskew */
+ s->opt[OPT_DESKEW].name = "deskew";
+ s->opt[OPT_DESKEW].title = SANE_I18N ("Deskew");
+ s->opt[OPT_DESKEW].desc = SANE_I18N ("Rotate image so it appears upright.");
+ s->opt[OPT_DESKEW].type = SANE_TYPE_BOOL;
+ s->opt[OPT_DESKEW].unit = SANE_UNIT_NONE;
+ s->opt[OPT_DESKEW].size = sizeof (SANE_Bool);
+ s->opt[OPT_DESKEW].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_DESKEW].cap |= SANE_CAP_ADVANCED;
+ if ( dip_has_deskew (s->dip, s->hw) )
+ {
+ s->opt[OPT_DESKEW].cap &= ~SANE_CAP_INACTIVE;
+ }
+ else
+ {
+ s->opt[OPT_DESKEW].cap |= SANE_CAP_INACTIVE;
+ }
+ s->opt[OPT_DESKEW].constraint_type = SANE_CONSTRAINT_NONE;
+ s->val[OPT_DESKEW].w = SANE_FALSE;
+
+ /* auto-crop */
+ s->opt[OPT_AUTOCROP].name = "autocrop";
+ s->opt[OPT_AUTOCROP].title = SANE_I18N ("Trim image to paper size");
+ s->opt[OPT_AUTOCROP].desc = SANE_I18N ("Determines empty margins in the scanned image and removes them. This normally reduces the image to the size of the original document but may remove more.");
+ s->opt[OPT_AUTOCROP].type = SANE_TYPE_BOOL;
+ s->opt[OPT_AUTOCROP].unit = SANE_UNIT_NONE;
+ s->opt[OPT_AUTOCROP].size = sizeof (SANE_Bool);
+ s->opt[OPT_AUTOCROP].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_AUTOCROP].cap |= SANE_CAP_ADVANCED;
+ if ( dip_has_autocrop (s->dip, s->hw) )
+ {
+ s->opt[OPT_AUTOCROP].cap &= ~SANE_CAP_INACTIVE;
+ }
+ else
+ {
+ s->opt[OPT_AUTOCROP].cap |= SANE_CAP_INACTIVE;
+ }
+ s->opt[OPT_AUTOCROP].constraint_type = SANE_CONSTRAINT_NONE;
+ s->val[OPT_AUTOCROP].w = SANE_FALSE;
+
+ s->opt[OPT_CALIBRATE].name = SANE_EPSON_CALIBRATE_NAME;
+ s->opt[OPT_CALIBRATE].title = SANE_EPSON_CALIBRATE_TITLE;
+ s->opt[OPT_CALIBRATE].desc = SANE_EPSON_CALIBRATE_DESC;
+ s->opt[OPT_CALIBRATE].type = SANE_TYPE_BUTTON;
+ s->opt[OPT_CALIBRATE].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CALIBRATE].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_CALIBRATE].constraint_type = SANE_CONSTRAINT_NONE;
+ if (!maintenance_is_supported (s->hw))
+ {
+ s->opt[OPT_CALIBRATE].cap |= SANE_CAP_INACTIVE;
+ }
+
+ s->opt[OPT_CLEAN].name = SANE_EPSON_CLEAN_NAME;
+ s->opt[OPT_CLEAN].title = SANE_EPSON_CLEAN_TITLE;
+ s->opt[OPT_CLEAN].desc = SANE_EPSON_CLEAN_DESC;
+ s->opt[OPT_CLEAN].type = SANE_TYPE_BUTTON;
+ s->opt[OPT_CLEAN].unit = SANE_UNIT_NONE;
+ s->opt[OPT_CLEAN].cap |= SANE_CAP_ADVANCED;
+ s->opt[OPT_CLEAN].constraint_type = SANE_CONSTRAINT_NONE;
+ if (!maintenance_is_supported (s->hw))
+ {
+ s->opt[OPT_CLEAN].cap |= SANE_CAP_INACTIVE;
+ }
+
+ handle_mode (s, s->val[OPT_MODE].w, &reload);
+ change_profile_matrix (s);
+
+ /* adjust default settings based on configuration options */
+ {
+ void *cfg = cfg_init (NULL, NULL);
+ if (cfg_has_value (cfg, CFG_KEY_OPTION, "prefer-adf"))
+ {
+ const char *found = NULL;
+ int i = 0;
+ while ((found = s->hw->sources[i])
+ && 0 != strcmp_c (ADF_STR, found))
+ {
+ ++i;
+ }
+ if (found) handle_source (s, i, ADF_STR);
+ }
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+
+SANE_Status
+epkowa_open (const char *name, SANE_Handle *handle, const void *dip)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+ Epson_Scanner *s = NULL;
+
+ status = create_sane_handle (&s, name, dip);
+ if (SANE_STATUS_GOOD != status) return status;
+
+ /* insert newly opened handle into list of open handles */
+ s->next = first_handle;
+ first_handle = s;
+
+ *handle = (SANE_Handle) s;
+
+ return SANE_STATUS_GOOD;
+}
+
+
+void
+sane_close (SANE_Handle handle)
+{
+ Epson_Scanner *s, *prev;
+ size_t i;
+
+ /* Test if there is still data pending from the scanner. If so, then
+ * do a cancel.
+ */
+
+ log_call ();
+
+ s = (Epson_Scanner *) handle;
+
+ /* remove handle from list of open handles */
+ prev = 0;
+ for (s = first_handle; s; s = s->next)
+ {
+ if (s == handle)
+ break;
+ prev = s;
+ }
+
+ if (!s)
+ {
+ err_fatal ("invalid handle (0x%p)", handle);
+ return;
+ }
+
+ if (prev)
+ prev->next = s->next;
+ else
+ first_handle = s->next;
+
+ s->hw = dev_dtor (s->hw);
+
+ const_delete (s->opt[OPT_BIT_DEPTH].constraint.word_list, SANE_Word *);
+ const_delete (s->opt[OPT_SCAN_AREA].constraint.string_list,
+ SANE_String_Const *);
+
+ /* image data acquisition related resources */
+ delete (s->raw.buf);
+ delete (s->img.buf);
+ for (i = 0; i < LINES_SHUFFLE_MAX; ++i)
+ delete (s->line_buffer[i]);
+
+ dip_destroy_LUT (s->dip, s->lut);
+
+ delete (s);
+}
+
+
+const SANE_Option_Descriptor *
+sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
+{
+ Epson_Scanner *s = (Epson_Scanner *) handle;
+
+ if (option < 0 || option >= NUM_OPTIONS)
+ {
+ log_call ("(%d)", option);
+ return NULL;
+ }
+
+ log_call ("(%s)", s->opt[option].name);
+ return (s->opt + option);
+}
+
+
+static const SANE_String_Const *
+search_string_list (const SANE_String_Const * list, SANE_String value)
+{
+ log_call ("(%s)", value);
+
+ while (*list != NULL && strcmp_c (value, *list) != 0)
+ {
+ ++list;
+ }
+
+ return ((*list == NULL) ? NULL : list);
+}
+
+
+/* Activate, deactivate an option. Subroutines so we can add
+ debugging info if we want. The change flag is set to TRUE
+ if we changed an option. If we did not change an option,
+ then the value of the changed flag is not modified.
+ */
+static void
+activateOption (Epson_Scanner * s, SANE_Int option, SANE_Bool * change)
+{
+ log_call ("(%s)", s->opt[option].name);
+ if (!SANE_OPTION_IS_ACTIVE (s->opt[option].cap))
+ {
+ s->opt[option].cap &= ~SANE_CAP_INACTIVE;
+ *change = SANE_TRUE;
+ }
+}
+
+static void
+deactivateOption (Epson_Scanner * s, SANE_Int option, SANE_Bool * change)
+{
+ log_call ("(%s)", s->opt[option].name);
+ if (SANE_OPTION_IS_ACTIVE (s->opt[option].cap))
+ {
+ s->opt[option].cap |= SANE_CAP_INACTIVE;
+ *change = SANE_TRUE;
+ }
+}
+
+static void
+setOptionState (Epson_Scanner * s, SANE_Bool state,
+ SANE_Int option, SANE_Bool * change)
+{
+ if (state)
+ {
+ activateOption (s, option, change);
+ }
+ else
+ {
+ deactivateOption (s, option, change);
+ }
+}
+
+static void
+calculate_scan_area_offset (const Option_Value *v, int *left, int *top)
+{
+ *left =
+ SANE_UNFIX (v[OPT_TL_X].w) / MM_PER_INCH *
+ v[OPT_X_RESOLUTION].w * v[OPT_ZOOM].w / 100 + 0.5;
+
+ *top =
+ SANE_UNFIX (v[OPT_TL_Y].w) / MM_PER_INCH *
+ v[OPT_Y_RESOLUTION].w * v[OPT_ZOOM].w / 100 + 0.5;
+}
+
+static void
+calculate_scan_area_max (const Epson_Scanner *s, int *x, int *y)
+{
+ /* Cast first value to double to force the whole computation to be
+ done in double. Works around integer overflows.
+ */
+ *x = ((double) s->hw->src->max_x * s->val[OPT_X_RESOLUTION].w *
+ s->val[OPT_ZOOM].w / (s->hw->base_res * 100));
+ *y = ((double) s->hw->src->max_y * s->val[OPT_Y_RESOLUTION].w *
+ s->val[OPT_ZOOM].w / (s->hw->base_res * 100));
+}
+
+static bool
+scan_area_is_valid (Epson_Scanner *s)
+{
+ int left = 0;
+ int top = 0;
+ int max_x = 0;
+ int max_y = 0;
+
+ bool rv = true;
+
+ /* finalize parameters before we validate*/
+ estimate_parameters (s, NULL);
+
+ calculate_scan_area_max (s, &max_x, &max_y);
+ calculate_scan_area_offset (s->val, &left, &top);
+
+ if ( s->raw.ctx.pixels_per_line > max_x) rv = false;
+ if ((left + s->raw.ctx.pixels_per_line) > max_x) rv = false;
+
+ if (!need_autocrop_override (s))
+ {
+ if ( s->raw.ctx.lines > max_y ) rv = false;
+ if ((top + s->raw.ctx.lines) > max_y) rv = false;
+ }
+
+ /* check physical channel limitations */
+ {
+ size_t max_req = s->hw->channel->max_request_size (s->hw->channel);
+ if (s->raw.ctx.bytes_per_line > max_req) rv = false;
+ }
+
+ if (s->hw->using_fs)
+ {
+ if (s->raw.ctx.pixels_per_line > s->hw->scan_width_limit) rv = false;
+ return rv;
+ }
+
+ if (SANE_FRAME_RGB == s->raw.ctx.format) /* max x according to to spec */
+ if (s->raw.ctx.pixels_per_line > 21840) rv = false;
+ if (top > 65530) rv = false;
+ if (left > 65530) rv = false;
+
+ return rv;
+}
+
+static SANE_Status
+getvalue (Epson_Scanner *s, SANE_Int option, void *value)
+{
+ SANE_Option_Descriptor *sopt = &(s->opt[option]);
+ Option_Value *sval = &(s->val[option]);
+
+ log_call ("(%s)", sopt->name);
+
+ switch (option)
+ {
+ case OPT_GAMMA_VECTOR_R:
+ case OPT_GAMMA_VECTOR_G:
+ case OPT_GAMMA_VECTOR_B:
+ memcpy (value, sval->wa, sopt->size);
+ break;
+
+ case OPT_NUM_OPTS:
+ case OPT_RESOLUTION:
+ case OPT_X_RESOLUTION:
+ case OPT_Y_RESOLUTION:
+ case OPT_TL_X:
+ case OPT_TL_Y:
+ case OPT_BR_X:
+ case OPT_BR_Y:
+ case OPT_MIRROR:
+ case OPT_SPEED:
+ case OPT_PREVIEW_SPEED:
+ case OPT_AAS:
+ case OPT_PREVIEW:
+ case OPT_BRIGHTNESS:
+ case OPT_CONTRAST:
+ case OPT_SHARPNESS:
+ case OPT_AUTO_EJECT:
+ case OPT_CCT_1:
+ case OPT_CCT_2:
+ case OPT_CCT_3:
+ case OPT_CCT_4:
+ case OPT_CCT_5:
+ case OPT_CCT_6:
+ case OPT_CCT_7:
+ case OPT_CCT_8:
+ case OPT_CCT_9:
+ case OPT_THRESHOLD:
+ case OPT_ZOOM:
+ case OPT_BIT_DEPTH:
+ case OPT_WAIT_FOR_BUTTON:
+ case OPT_DETECT_DOC_SIZE:
+ case OPT_LIMIT_RESOLUTION:
+ case OPT_ADF_AUTO_SCAN:
+ case OPT_DESKEW:
+ case OPT_AUTOCROP:
+ case OPT_NEEDS_POLLING:
+ *((SANE_Word *) value) = sval->w;
+ break;
+ case OPT_MODE:
+ case OPT_ADF_MODE:
+ case OPT_HALFTONE:
+ case OPT_DROPOUT:
+ case OPT_BRIGHTNESS_METHOD:
+ case OPT_SCAN_AREA:
+ case OPT_SOURCE:
+ case OPT_FILM_TYPE:
+ case OPT_GAMMA_CORRECTION:
+ case OPT_COLOR_CORRECTION:
+ case OPT_BAY:
+ case OPT_FOCUS:
+ case OPT_ADF_DFD_SENSITIVITY:
+ strcpy ((char *) value, sopt->constraint.string_list[sval->w]);
+ break;
+
+ case OPT_QUICK_FORMAT:
+ getvalue (s, OPT_SCAN_AREA, value);
+ break;
+
+ case OPT_EXT_SANE_STATUS:
+ if (using (s->hw, adf)
+ && (ADF_EXT_STATUS_DFE & s->hw->adf->ext_status))
+ sval->w = EXT_SANE_STATUS_MULTI_FEED;
+ if (using (s->hw, adf)
+ && (ADF_EXT_STATUS_TR_OPN & s->hw->adf->ext_status))
+ sval->w = EXT_SANE_STATUS_TRAY_CLOSED;
+ *((SANE_Word *) value) = sval->w;
+ sval->w = 0;
+ break;
+
+ case OPT_MONITOR_BUTTON:
+ if (SANE_OPTION_IS_ACTIVE (option))
+ {
+ SANE_Bool pressed;
+ SANE_Status status = SANE_STATUS_GOOD;
+ if (SANE_STATUS_GOOD == status)
+ {
+ status = get_push_button_status (s->hw, &pressed);
+ if (SANE_STATUS_GOOD == status)
+ {
+ *((SANE_Bool *) value) = pressed;
+ }
+ }
+ return status;
+ }
+ else
+ {
+ return SANE_STATUS_UNSUPPORTED;
+ }
+ break;
+ case OPT_POLLING_TIME:
+ *((SANE_Word *) value) = sval->w;
+ break;
+ case OPT_SCAN_AREA_IS_VALID:
+ {
+ sval->w = scan_area_is_valid (s);
+ *((SANE_Word *) value) = sval->w;
+ }
+ break;
+ case OPT_ADF_DUPLEX_DIRECTION_MATCHES:
+ {
+ sval->w = adf_duplex_direction_matches (s->hw);
+ *((SANE_Word *) value) = sval->w;
+ }
+ break;
+ default:
+ return SANE_STATUS_INVAL;
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+
+static void
+handle_mode (Epson_Scanner * s, SANE_Int optindex, SANE_Bool * reload)
+{
+ SANE_Bool dropout, aas, halftone, threshold, cct;
+ SANE_Bool brightness, contrast;
+
+ log_call ();
+
+ *reload = SANE_FALSE;
+
+ switch (optindex)
+ {
+ case 0: /* b & w */
+ dropout = SANE_TRUE;
+ aas = SANE_TRUE;
+ halftone = SANE_TRUE;
+ threshold = SANE_TRUE;
+ cct = SANE_FALSE;
+ brightness = SANE_FALSE;
+ contrast = SANE_FALSE;
+ break;
+ case 1: /* gray */
+ dropout = SANE_TRUE;
+ aas = SANE_FALSE;
+ halftone = SANE_FALSE;
+ threshold = SANE_FALSE;
+ cct = SANE_FALSE;
+ brightness = SANE_TRUE;
+ contrast = SANE_TRUE;
+ break;
+ case 2: /* color */
+ dropout = SANE_FALSE;
+ aas = SANE_FALSE;
+ halftone = SANE_FALSE;
+ threshold = SANE_FALSE;
+ cct = SANE_TRUE;
+ brightness = SANE_TRUE;
+ contrast = SANE_TRUE;
+ break;
+ default:
+ return;
+ }
+
+ if (s->hw->cmd->level[0] == 'D')
+ {
+ dropout = SANE_FALSE;
+ aas = SANE_FALSE;
+ halftone = SANE_FALSE;
+ }
+
+ setOptionState (s, dropout, OPT_DROPOUT, reload);
+ s->val[OPT_DROPOUT].w = 0;
+ setOptionState (s, halftone, OPT_HALFTONE, reload);
+ s->val[OPT_HALFTONE].w = 0;
+ setOptionState (s, aas, OPT_AAS, reload);
+ s->val[OPT_AAS].w = SANE_FALSE;
+
+ setOptionState (s, threshold, OPT_THRESHOLD, reload);
+
+ setOptionState (s, brightness, OPT_BRIGHTNESS, reload);
+ setOptionState (s, contrast, OPT_CONTRAST, reload);
+
+ setOptionState (s, cct, OPT_CCT_1, reload);
+ setOptionState (s, cct, OPT_CCT_2, reload);
+ setOptionState (s, cct, OPT_CCT_3, reload);
+ setOptionState (s, cct, OPT_CCT_4, reload);
+ setOptionState (s, cct, OPT_CCT_5, reload);
+ setOptionState (s, cct, OPT_CCT_6, reload);
+ setOptionState (s, cct, OPT_CCT_7, reload);
+ setOptionState (s, cct, OPT_CCT_8, reload);
+ setOptionState (s, cct, OPT_CCT_9, reload);
+
+ /* if binary, then disable the bit depth selection */
+ if (optindex == 0)
+ {
+ s->opt[OPT_BIT_DEPTH].cap |= SANE_CAP_INACTIVE;
+ }
+ else
+ {
+ if (bitDepthList[0] == 1)
+ s->opt[OPT_BIT_DEPTH].cap |= SANE_CAP_INACTIVE;
+ else
+ {
+ s->opt[OPT_BIT_DEPTH].cap &= ~SANE_CAP_INACTIVE;
+ s->val[OPT_BIT_DEPTH].w = mode_params[optindex].depth;
+ }
+ }
+
+ if (optindex == 0) /* b & w */
+ handle_depth_halftone (s, 0, reload);
+
+ *reload = SANE_TRUE;
+}
+
+
+static void
+change_profile_matrix (Epson_Scanner * s)
+{
+ int index = 0;
+
+ log_call ();
+
+ require (s->hw->scan_hard);
+
+ if (using (s->hw, tpu)) /* TPU */
+ {
+ if (s->val[OPT_FILM_TYPE].w == 0) /* posi */
+ index = 3;
+ else
+ index = 1;
+ }
+ else /* Flatbed or ADF */
+ {
+ index = 0;
+ }
+
+ s->val[OPT_CCT_1].w = SANE_FIX (s->hw->scan_hard->color_profile[index][0]);
+ s->val[OPT_CCT_2].w = SANE_FIX (s->hw->scan_hard->color_profile[index][1]);
+ s->val[OPT_CCT_3].w = SANE_FIX (s->hw->scan_hard->color_profile[index][2]);
+ s->val[OPT_CCT_4].w = SANE_FIX (s->hw->scan_hard->color_profile[index][3]);
+ s->val[OPT_CCT_5].w = SANE_FIX (s->hw->scan_hard->color_profile[index][4]);
+ s->val[OPT_CCT_6].w = SANE_FIX (s->hw->scan_hard->color_profile[index][5]);
+ s->val[OPT_CCT_7].w = SANE_FIX (s->hw->scan_hard->color_profile[index][6]);
+ s->val[OPT_CCT_8].w = SANE_FIX (s->hw->scan_hard->color_profile[index][7]);
+ s->val[OPT_CCT_9].w = SANE_FIX (s->hw->scan_hard->color_profile[index][8]);
+}
+
+
+static unsigned char
+int2cpt (int val)
+{
+ if (val >= 0)
+ {
+ if (val > 127)
+ val = 127;
+ return (unsigned char) val;
+ }
+ else
+ {
+ val = -val;
+ if (val > 127)
+ val = 127;
+ return (unsigned char) (0x80 | val);
+ }
+}
+
+
+static void
+get_colorcoeff_from_profile (double *profile, unsigned char *color_coeff)
+{
+ int cc_idx[] = { 4, 1, 7, 3, 0, 6, 5, 2, 8 };
+ int color_table[9];
+ int i;
+
+ round_cct (profile, color_table);
+
+ for (i = 0; i < 9; i++)
+ color_coeff[i] = int2cpt (color_table[cc_idx[i]]);
+}
+
+
+static void
+round_cct (double org_cct[], int rnd_cct[])
+{
+ int i, j, index;
+ double mult_cct[9], frac[9];
+ int sum[3];
+ int loop;
+
+ for (i = 0; i < 9; i++)
+ mult_cct[i] = org_cct[i] * 32;
+
+ for (i = 0; i < 9; i++)
+ rnd_cct[i] = (int) floor (org_cct[i] * 32 + 0.5);
+
+ loop = 0;
+
+ do
+ {
+ for (i = 0; i < 3; i++)
+ {
+ if ((rnd_cct[i * 3] == 11) &&
+ (rnd_cct[i * 3] == rnd_cct[i * 3 + 1]) &&
+ (rnd_cct[i * 3] == rnd_cct[i * 3 + 2]))
+ {
+ rnd_cct[i * 3 + i]--;
+ mult_cct[i * 3 + i] = rnd_cct[i * 3 + i];
+ }
+ }
+
+ for (i = 0; i < 3; i++)
+ {
+ sum[i] = 0;
+ for (j = 0; j < 3; j++)
+ sum[i] += rnd_cct[i * 3 + j];
+ }
+
+ for (i = 0; i < 9; i++)
+ frac[i] = mult_cct[i] - rnd_cct[i];
+
+ for (i = 0; i < 3; i++)
+ {
+ if (sum[i] < 32)
+ {
+ index = get_roundup_index (&frac[i * 3], 3);
+ if (index != -1)
+ {
+ rnd_cct[i * 3 + index]++;
+ mult_cct[i * 3 + index] = rnd_cct[i * 3 + index];
+ sum[i]++;
+ }
+ }
+ else if (sum[i] > 32)
+ {
+ index = get_rounddown_index (&frac[i * 3], 3);
+ if (index != -1)
+ {
+ rnd_cct[i * 3 + index]--;
+ mult_cct[i * 3 + index] = rnd_cct[i * 3 + index];
+ sum[i]--;
+ }
+ }
+ }
+ }
+ while ((++loop < 2)
+ && ((sum[0] != 32) || (sum[1] != 32) || (sum[2] != 32)));
+}
+
+
+static int
+get_roundup_index (double frac[], int n)
+{
+ int i, index = -1;
+ double max_val = 0.0;
+
+ for (i = 0; i < n; i++)
+ {
+ if (frac[i] < 0)
+ continue;
+ if (max_val < frac[i])
+ {
+ index = i;
+ max_val = frac[i];
+ }
+ }
+
+ return index;
+}
+
+
+static int
+get_rounddown_index (double frac[], int n)
+{
+ int i, index = -1;
+ double min_val = 1.0;
+
+ for (i = 0; i < n; i++)
+ {
+ if (frac[i] > 0)
+ continue;
+ if (min_val > frac[i])
+ {
+ index = i;
+ min_val = frac[i];
+ }
+ }
+
+ return index;
+}
+
+
+/* This routine handles common options between OPT_MODE and
+ OPT_HALFTONE. These options are TET (a HALFTONE mode), AAS
+ - auto area segmentation, and threshold. Apparently AAS
+ is some method to differentiate between text and photos.
+ Or something like that.
+
+ AAS is available when the scan color depth is 1 and the
+ halftone method is not TET.
+
+ Threshold is available when halftone is NONE, and depth is 1.
+*/
+static void
+handle_depth_halftone (Epson_Scanner * s, SANE_Int optindex,
+ SANE_Bool * reload)
+{
+ SANE_Bool threshold, aas, dropout;
+
+ log_call ();
+
+ *reload = SANE_FALSE;
+
+ switch (halftone_params[optindex])
+ {
+ case HALFTONE_NONE:
+ threshold = SANE_TRUE;
+ aas = SANE_TRUE;
+ dropout = SANE_TRUE;
+ break;
+ case HALFTONE_TET:
+ threshold = SANE_FALSE;
+ aas = SANE_FALSE;
+ dropout = SANE_FALSE;
+ break;
+ default:
+ threshold = SANE_FALSE;
+ aas = SANE_TRUE;
+ dropout = SANE_TRUE;
+ }
+
+ setOptionState (s, threshold, OPT_THRESHOLD, reload);
+ setOptionState (s, aas, OPT_AAS, reload);
+ setOptionState (s, dropout, OPT_DROPOUT, reload);
+
+ *reload = SANE_TRUE;
+}
+
+
+static void
+handle_resolution (Epson_Scanner * s, SANE_Int option, SANE_Word value)
+{
+ SANE_Int *last = NULL;
+
+ SANE_Int size = 0;
+ SANE_Word *list = NULL;
+
+ int f = 0;
+ int k = 0;
+ int n = 0;
+
+ log_call ("(%s, %d)", s->opt[option].name, value);
+
+ switch (option)
+ {
+ case OPT_RESOLUTION:
+ last = &s->hw->res.last;
+ size = s->hw->res.size;
+ list = s->hw->res.list;
+ break;
+ case OPT_X_RESOLUTION:
+ last = &s->hw->res_x.last;
+ size = s->hw->res_x.size;
+ list = s->hw->res_x.list;
+ break;
+ case OPT_Y_RESOLUTION:
+ last = &s->hw->res_y.last;
+ size = s->hw->res_y.size;
+ list = s->hw->res_y.list;
+ break;
+ default:
+ err_fatal ("%s", strerror (EINVAL));
+ exit (EXIT_FAILURE);
+ }
+
+ if (SANE_CONSTRAINT_RANGE == s->opt[option].constraint_type)
+ {
+ sanei_constrain_value (&(s->opt[option]), &value, NULL);
+ s->val[option].w = value;
+ }
+ else
+ {
+ SANE_Int best = list[size];
+ int min_d = INT_MAX;
+
+ /* find supported resolution closest to that requested */
+ for (n = 1; n <= size; n++)
+ {
+ int d = abs (value - list[n]);
+
+ if (d < min_d)
+ {
+ min_d = d;
+ k = n;
+ best = list[n];
+ }
+ }
+
+ /* FIXME? what's this trying to do? Use a resolution close to the
+ last one used if best is far away? Why??? */
+ if ((value != best) && *last)
+ {
+ for (f = 1; f <= size; f++)
+ if (*last == list[f])
+ break;
+
+ if (f != k && f != k - 1 && f != k + 1)
+ {
+ if (k > f)
+ best = list[f + 1];
+ else if (k < f)
+ best = list[f - 1];
+ }
+ }
+
+ *last = best;
+ s->val[option].w = (SANE_Word) best;
+ }
+
+ if (OPT_RESOLUTION == option)
+ {
+ s->val[OPT_X_RESOLUTION].w = s->val[option].w;
+ s->val[OPT_Y_RESOLUTION].w = s->val[option].w;
+
+ s->hw->res_x.last = s->hw->res.last;
+ s->hw->res_y.last = s->hw->res.last;
+ }
+ {
+ SANE_Bool dummy;
+ handle_deskew (s, NULL, &dummy);
+ }
+}
+
+static void
+limit_adf_res (Epson_Scanner * s)
+{
+ SANE_Constraint_Type type = s->opt[OPT_RESOLUTION].constraint_type;
+ int limit = large_res_kills_adf_scan (s->hw);
+
+ if (using (s->hw, adf))
+ {
+ dev_limit_res (s->hw, type, limit);
+
+ /* constrain the current values to the new limit */
+ handle_resolution (s, OPT_RESOLUTION, s->val[OPT_RESOLUTION].w);
+ handle_resolution (s, OPT_X_RESOLUTION, s->val[OPT_X_RESOLUTION].w);
+ handle_resolution (s, OPT_Y_RESOLUTION, s->val[OPT_Y_RESOLUTION].w);
+ }
+ else
+ {
+ dev_restore_res (s->hw, type);
+ }
+}
+
+
+/*
+ Handles setting the source (flatbed, transparency adapter (TPU),
+ or auto document feeder (ADF)).
+
+ For newer scanners it also sets the focus according to the
+ glass / TPU settings.
+*/
+static SANE_Status
+handle_source (Epson_Scanner * s, SANE_Int optindex, char *value)
+{
+ SANE_Bool dummy;
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ log_call ("(%s)", value);
+
+ if (s->val[OPT_SOURCE].w == optindex)
+ return SANE_STATUS_GOOD;
+
+ if (s->hw->adf && strcmp_c (ADF_STR, value) == 0)
+ {
+ s->val[OPT_SOURCE].w = optindex;
+ s->hw->src = (const extension *) s->hw->adf;
+ deactivateOption (s, OPT_FILM_TYPE, &dummy);
+ s->val[OPT_FOCUS].w = 0;
+ if (EXT_STATUS_ADFS & s->hw->ext_status)
+ {
+ activateOption (s, OPT_ADF_MODE, &dummy);
+ activateOption (s, OPT_ADF_DUPLEX_DIRECTION_MATCHES, &dummy);
+ }
+ else
+ {
+ deactivateOption (s, OPT_ADF_MODE, &dummy);
+ s->val[OPT_ADF_MODE].w = 0;
+ deactivateOption (s, OPT_ADF_DUPLEX_DIRECTION_MATCHES, &dummy);
+ }
+ if (FSI_CAP_ADFAS & s->hw->fsi_cap_2)
+ {
+ activateOption (s, OPT_ADF_AUTO_SCAN, &dummy);
+ }
+ if (FSI_CAP_DFD & s->hw->fsi_cap_2)
+ {
+ activateOption (s, OPT_ADF_DFD_SENSITIVITY, &dummy);
+ }
+ else
+ {
+ deactivateOption (s, OPT_ADF_DFD_SENSITIVITY, &dummy);
+ s->val[OPT_ADF_DFD_SENSITIVITY].w = 0;
+ }
+ }
+ else if (s->hw->tpu && strcmp_c (TPU_STR, value) == 0)
+ {
+ s->val[OPT_SOURCE].w = optindex;
+ s->hw->src = (const extension *) s->hw->tpu;
+ deactivateOption (s, OPT_ADF_MODE, &dummy);
+ deactivateOption (s, OPT_ADF_AUTO_SCAN, &dummy);
+ deactivateOption (s, OPT_ADF_DFD_SENSITIVITY, &dummy);
+ deactivateOption (s, OPT_EJECT, &dummy);
+ deactivateOption (s, OPT_AUTO_EJECT, &dummy);
+ deactivateOption (s, OPT_ADF_DUPLEX_DIRECTION_MATCHES, &dummy);
+ }
+ else if (s->hw->fbf)
+ {
+ s->val[OPT_SOURCE].w = optindex;
+ s->hw->src = (const extension *) s->hw->fbf;
+ s->val[OPT_FOCUS].w = 0;
+ deactivateOption (s, OPT_ADF_MODE, &dummy);
+ deactivateOption (s, OPT_ADF_AUTO_SCAN, &dummy);
+ deactivateOption (s, OPT_ADF_DFD_SENSITIVITY, &dummy);
+ deactivateOption (s, OPT_ADF_DUPLEX_DIRECTION_MATCHES, &dummy);
+ }
+ else
+ {
+ err_fatal ("internal inconsistency");
+ return SANE_STATUS_INVAL;
+ }
+
+ /* reset the scanner when we are changing the source setting -
+ this is necessary for the Perfection 1650 */
+ if (s->hw->need_reset_on_source_change)
+ initialize (s->hw);
+
+ handle_detect_doc_size (s, NULL, &dummy);
+
+ /*change*/
+ status = handle_scan_area(s, s->val[OPT_ADF_MODE].w);
+
+ change_profile_matrix (s);
+
+ setOptionState (s, using (s->hw, tpu), OPT_FILM_TYPE, &dummy);
+ setOptionState (s, using (s->hw, adf), OPT_AUTO_EJECT, &dummy);
+ setOptionState (s, using (s->hw, adf), OPT_EJECT, &dummy);
+
+ if (s->hw->cmd->set_focus_position)
+ {
+ if (using (s->hw, tpu))
+ {
+ s->val[OPT_FOCUS].w = 1;
+ setOptionState (s, SANE_TRUE, OPT_FOCUS, &dummy);
+ }
+ else if (using (s->hw, adf))
+ {
+ s->val[OPT_FOCUS].w = 0;
+ setOptionState (s, SANE_FALSE, OPT_FOCUS, &dummy);
+ }
+ else
+ {
+ s->val[OPT_FOCUS].w = 0;
+ setOptionState (s, SANE_TRUE, OPT_FOCUS, &dummy);
+ }
+ }
+
+ status = get_resolution_constraints (s->hw, s);
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+
+ if (s->hw->adf)
+ {
+ if (large_res_kills_adf_scan (s->hw)) limit_adf_res (s);
+
+ if (zoom_kills_adf_scan (s->hw))
+ {
+ if (using (s->hw, adf))
+ {
+ s->val[OPT_ZOOM].w = 100;
+ deactivateOption (s, OPT_ZOOM, &dummy);
+ }
+ else
+ {
+ if (s->hw->cmd->set_zoom)
+ activateOption (s, OPT_ZOOM, &dummy);
+ }
+ }
+ }
+
+ return status;
+}
+
+
+static void
+handle_filmtype (Epson_Scanner * s, SANE_Int optindex, char *value)
+{
+ log_call ();
+
+ value = value;
+
+ if (!s->hw->tpu || s->val[OPT_FILM_TYPE].w == optindex)
+ return;
+
+ s->val[OPT_FILM_TYPE].w = optindex;
+
+ require (s->hw->src == (extension *) s->hw->tpu);
+
+ s->val[OPT_TL_X].w = 0;
+ s->val[OPT_TL_Y].w = 0;
+ s->val[OPT_BR_X].w = s->hw->src->x_range.max;
+ s->val[OPT_BR_Y].w = s->hw->src->y_range.max;
+
+ s->opt[OPT_TL_X].constraint.range = &(s->hw->src->x_range);
+ s->opt[OPT_TL_Y].constraint.range = &(s->hw->src->y_range);
+ s->opt[OPT_BR_X].constraint.range = &(s->hw->src->x_range);
+ s->opt[OPT_BR_Y].constraint.range = &(s->hw->src->y_range);
+
+ change_profile_matrix (s);
+}
+
+static void
+handle_autocrop (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload)
+{
+ *reload = SANE_FALSE;
+
+ if ( dip_has_autocrop (s->dip, s->hw)
+ && ( ! SANE_OPTION_IS_ACTIVE (s->opt[OPT_DESKEW].cap)
+ || ! s->val[OPT_DESKEW].b )
+ && !s->val[OPT_PREVIEW].b )
+ {
+ activateOption (s, OPT_AUTOCROP, reload);
+ if (value)
+ {
+ SANE_Bool dummy;
+ s->val[OPT_AUTOCROP].b = *value;
+ handle_deskew (s, NULL, &dummy);
+ *reload = SANE_TRUE;
+ }
+ }
+ else
+ {
+ deactivateOption (s, OPT_AUTOCROP, reload);
+ }
+}
+
+static void
+handle_deskew (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload)
+{
+ *reload = SANE_FALSE;
+
+ if ( dip_has_deskew (s->dip, s->hw)
+ && ( ! SANE_OPTION_IS_ACTIVE (s->opt[OPT_AUTOCROP].cap)
+ || ! s->val[OPT_AUTOCROP].b )
+ && ( ! SANE_OPTION_IS_ACTIVE (s->opt[OPT_BIT_DEPTH].cap)
+ || 8 == s->val[OPT_BIT_DEPTH].w)
+ && 600 >= s->val[OPT_RESOLUTION].w
+ && 600 >= s->val[OPT_X_RESOLUTION].w
+ && 600 >= s->val[OPT_Y_RESOLUTION].w
+ && !s->val[OPT_PREVIEW].b )
+ {
+ activateOption (s, OPT_DESKEW , reload);
+ if (value)
+ {
+ SANE_Bool dummy;
+ s->val[OPT_DESKEW].b = *value;
+ handle_autocrop (s, NULL, &dummy);
+ *reload = SANE_TRUE;
+ }
+ }
+ else
+ {
+ deactivateOption (s, OPT_DESKEW, reload);
+ }
+}
+
+static void
+handle_detect_doc_size (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload)
+{
+ *reload = SANE_FALSE;
+
+ if (has_size_check_support (s->hw->src)
+ && !s->val[OPT_PREVIEW].b)
+ {
+ activateOption (s, OPT_DETECT_DOC_SIZE, reload);
+ if (value)
+ {
+ s->val[OPT_DETECT_DOC_SIZE].b = *value;
+ *reload = SANE_TRUE;
+ }
+ }
+ else
+ {
+ deactivateOption (s, OPT_DETECT_DOC_SIZE, reload);
+ }
+}
+
+static void
+handle_preview (Epson_Scanner *s, SANE_Bool *value, SANE_Bool *reload)
+{
+ SANE_Bool dummy;
+
+ if (value)
+ {
+ s->val[OPT_PREVIEW].b = *value;
+ handle_detect_doc_size (s, &s->val[OPT_DETECT_DOC_SIZE].b, &dummy);
+ handle_autocrop (s, &s->val[OPT_AUTOCROP].b, &dummy);
+ handle_deskew (s, &s->val[OPT_DESKEW].b, &dummy);
+ *reload = SANE_TRUE;
+ }
+}
+
+static SANE_Status
+setvalue (Epson_Scanner *s, SANE_Int option, void *value, SANE_Int * info)
+{
+ SANE_Option_Descriptor *sopt = &(s->opt[option]);
+ Option_Value *sval = &(s->val[option]);
+
+ SANE_Status status;
+ const SANE_String_Const *optval;
+ int optindex;
+ SANE_Bool reload = SANE_FALSE;
+
+ log_call ("(%s, value @%p)", sopt->name, value);
+
+ status = sanei_constrain_value (sopt, value, info);
+
+ if (status != SANE_STATUS_GOOD)
+ return status;
+
+ optval = NULL;
+ optindex = 0;
+
+ if (sopt->constraint_type == SANE_CONSTRAINT_STRING_LIST)
+ {
+ optval = search_string_list (sopt->constraint.string_list,
+ (char *) value);
+
+ if (optval == NULL)
+ return SANE_STATUS_INVAL;
+ optindex = optval - sopt->constraint.string_list;
+ }
+
+ switch (option)
+ {
+ case OPT_GAMMA_VECTOR_R:
+ case OPT_GAMMA_VECTOR_G:
+ case OPT_GAMMA_VECTOR_B:
+ memcpy (sval->wa, value, sopt->size); /* Word arrays */
+ break;
+
+ case OPT_CCT_1:
+ case OPT_CCT_2:
+ case OPT_CCT_3:
+ case OPT_CCT_4:
+ case OPT_CCT_5:
+ case OPT_CCT_6:
+ case OPT_CCT_7:
+ case OPT_CCT_8:
+ case OPT_CCT_9:
+ sval->w = *((SANE_Word *) value); /* Simple values */
+ break;
+
+ case OPT_FILM_TYPE:
+ handle_filmtype (s, optindex, (char *) value);
+ reload = SANE_TRUE;
+ break;
+
+ case OPT_DROPOUT:
+ case OPT_BAY:
+ case OPT_FOCUS:
+ sval->w = optindex; /* Simple lists */
+ break;
+
+ case OPT_EJECT:
+ dev_eject_paper (s->hw);
+ break;
+
+ case OPT_RESOLUTION:
+ case OPT_X_RESOLUTION:
+ case OPT_Y_RESOLUTION:
+ handle_resolution (s, option, *((SANE_Word *) value));
+ reload = SANE_TRUE;
+ break;
+
+ case OPT_TL_X:
+ case OPT_TL_Y:
+ case OPT_BR_X:
+ case OPT_BR_Y:
+ sval->w = *((SANE_Word *) value);
+ log_info ("set = %f", SANE_UNFIX (sval->w));
+ if (NULL != info)
+ *info |= SANE_INFO_RELOAD_PARAMS;
+ break;
+
+ case OPT_SOURCE:
+ status = handle_source (s, optindex, (char *) value);
+ reload = SANE_TRUE;
+ break;
+
+ case OPT_MODE:
+ {
+ if (sval->w == optindex)
+ break;
+
+ sval->w = optindex;
+
+ handle_mode (s, optindex, &reload);
+
+ break;
+ }
+
+ case OPT_ADF_MODE:
+ status = handle_scan_area(s, optindex);
+ reload = SANE_TRUE;
+ /* through intentionally */
+ case OPT_ADF_DFD_SENSITIVITY:
+ sval->w = optindex;
+ break;
+
+ case OPT_BIT_DEPTH:
+ {
+ SANE_Bool dummy;
+ sval->w = *((SANE_Word *) value);
+ mode_params[s->val[OPT_MODE].w].depth = sval->w;
+ handle_deskew (s, NULL, &dummy);
+ reload = SANE_TRUE;
+ break;
+ }
+ case OPT_HALFTONE:
+ if (sval->w == optindex)
+ break;
+ sval->w = optindex;
+ handle_depth_halftone (s, optindex, &reload);
+ break;
+
+ case OPT_BRIGHTNESS_METHOD:
+ {
+ const SANE_Range *r;
+ SANE_Bool f
+ = s->hw->gamma_user_defined[s->val[OPT_GAMMA_CORRECTION].w];
+
+ if (sval->w == optindex) break;
+
+ r = s->opt[OPT_BRIGHTNESS].constraint.range;
+
+ if (0 == strcmp_c (brightness_method_list[0], /* "hardware" */
+ sopt->constraint.string_list[optindex]))
+ {
+ s->opt[OPT_BRIGHTNESS].cap &= ~SANE_CAP_EMULATED;
+ s->opt[OPT_BRIGHTNESS].constraint.range = &s->hw->cmd->bright_range;
+ setOptionState (s, !f, OPT_BRIGHTNESS, &reload);
+ }
+ else
+ {
+ s->opt[OPT_BRIGHTNESS].cap |= SANE_CAP_EMULATED;
+ s->opt[OPT_BRIGHTNESS].constraint.range = &brightness_range;
+ setOptionState (s, SANE_TRUE, OPT_BRIGHTNESS, &reload);
+ }
+
+ if (r != s->opt[OPT_BRIGHTNESS].constraint.range)
+ {
+ double v = s->val[OPT_BRIGHTNESS].w;
+
+ /**/ if (0 < v)
+ {
+ require (0 < r->max);
+
+ v /= r->max;
+ r = s->opt[OPT_BRIGHTNESS].constraint.range;
+ v *= r->max;
+ v += 0.5;
+
+ reload = SANE_TRUE;
+ }
+ else if (0 > v)
+ {
+ require (0 > r->min);
+
+ v /= r->min;
+ r = s->opt[OPT_BRIGHTNESS].constraint.range;
+ v *= r->min;
+ v -= 0.5;
+
+ reload = SANE_TRUE;
+ }
+ else /* 0 == v */
+ {}
+
+ s->val[OPT_BRIGHTNESS].w = (SANE_Int) v;
+ }
+
+ sval->w = optindex;
+
+ break;
+ }
+
+ case OPT_COLOR_CORRECTION:
+ {
+ SANE_Bool f = s->hw->color_user_defined[optindex];
+
+ sval->w = optindex;
+ setOptionState (s, f, OPT_CCT_1, &reload);
+ setOptionState (s, f, OPT_CCT_2, &reload);
+ setOptionState (s, f, OPT_CCT_3, &reload);
+ setOptionState (s, f, OPT_CCT_4, &reload);
+ setOptionState (s, f, OPT_CCT_5, &reload);
+ setOptionState (s, f, OPT_CCT_6, &reload);
+ setOptionState (s, f, OPT_CCT_7, &reload);
+ setOptionState (s, f, OPT_CCT_8, &reload);
+ setOptionState (s, f, OPT_CCT_9, &reload);
+
+ break;
+ }
+
+ case OPT_GAMMA_CORRECTION:
+ {
+ SANE_Bool f = s->hw->gamma_user_defined[optindex];
+
+ sval->w = optindex;
+ setOptionState (s, f, OPT_GAMMA_VECTOR_R, &reload);
+ setOptionState (s, f, OPT_GAMMA_VECTOR_G, &reload);
+ setOptionState (s, f, OPT_GAMMA_VECTOR_B, &reload);
+
+ if (0 == strcmp_c (brightness_method_list[0], /* "hardware" */
+ s->opt[OPT_BRIGHTNESS_METHOD].constraint
+ .string_list[s->val[OPT_BRIGHTNESS_METHOD].w]))
+ {
+ setOptionState (s, !f, OPT_BRIGHTNESS, &reload);
+ }
+
+ break;
+ }
+
+ case OPT_AUTO_EJECT:
+ sval->w = *((SANE_Word *) value);
+ if (s->hw && s->hw->adf) s->hw->adf->auto_eject = sval->w;
+ break;
+ case OPT_MIRROR:
+ case OPT_SPEED:
+ case OPT_PREVIEW_SPEED:
+ case OPT_AAS:
+ case OPT_BRIGHTNESS:
+ case OPT_CONTRAST:
+ case OPT_SHARPNESS:
+ case OPT_THRESHOLD:
+ case OPT_ZOOM:
+ case OPT_WAIT_FOR_BUTTON:
+ case OPT_ADF_AUTO_SCAN:
+ sval->w = *((SANE_Word *) value);
+ break;
+
+ case OPT_DETECT_DOC_SIZE:
+ handle_detect_doc_size (s, (SANE_Word *) value, &reload);
+ break;
+
+ case OPT_PREVIEW:
+ handle_preview (s, (SANE_Word *) value, &reload);
+ break;
+
+ case OPT_DESKEW:
+ handle_deskew (s, (SANE_Word *) value, &reload);
+ break;
+
+ case OPT_AUTOCROP:
+ handle_autocrop (s, (SANE_Word *) value, &reload);
+ break;
+
+ case OPT_LIMIT_RESOLUTION:
+ sval->w = *((SANE_Word *) value);
+ filter_resolution_list (s);
+ reload = SANE_TRUE;
+ break;
+
+ case OPT_SCAN_AREA:
+ {
+ sval->w = optindex;
+
+ /**/ if (0 == strcmp_c (sopt->constraint.string_list[sval->w],
+ media_maximum))
+ {
+ s->val[OPT_TL_X].w = SANE_FIX (0.0);
+ s->val[OPT_TL_Y].w = SANE_FIX (0.0);
+ s->val[OPT_BR_X].w = s->hw->src->x_range.max;
+ s->val[OPT_BR_Y].w = s->hw->src->y_range.max;
+ }
+ else if (0 == strcmp_c (sopt->constraint.string_list[sval->w],
+ media_automatic))
+ {
+ SANE_Bool yes = SANE_TRUE;
+
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_DETECT_DOC_SIZE].cap))
+ {
+ setvalue (s, OPT_DETECT_DOC_SIZE, &yes, NULL);
+ }
+ }
+ else
+ {
+ size_t i = 0;
+
+ while (i < num_of (media_list)
+ && 0 != strcmp_c (sopt->constraint.string_list[sval->w],
+ media_list[i].name))
+ ++i;
+
+ require (i < num_of (media_list));
+
+ s->val[OPT_TL_X].w = SANE_FIX (0.0);
+ s->val[OPT_TL_Y].w = SANE_FIX (0.0);
+ s->val[OPT_BR_X].w = SANE_FIX (media_list[i].width);
+ s->val[OPT_BR_Y].w = SANE_FIX (media_list[i].height);
+
+ adf_handle_adjust_alignment (s, SANE_FALSE);
+ }
+
+ reload = SANE_TRUE;
+ break;
+ }
+ case OPT_QUICK_FORMAT:
+ setvalue (s, OPT_SCAN_AREA, value, info);
+ sval->w = s->val[OPT_SCAN_AREA].w;
+ break;
+
+ case OPT_CALIBRATE:
+ status = dev_calibrate (s->hw);
+ break;
+
+ case OPT_CLEAN:
+ status = dev_clean (s->hw);
+ break;
+
+ case OPT_MONITOR_BUTTON:
+ default:
+ return SANE_STATUS_INVAL;
+ }
+
+ if (reload && info != NULL)
+ {
+ *info |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS;
+ }
+
+ return status;
+}
+
+
+SANE_Status
+sane_control_option (SANE_Handle handle,
+ SANE_Int option,
+ SANE_Action action, void *value, SANE_Int * info)
+{
+ log_call ();
+
+ if (option < 0 || option >= NUM_OPTIONS)
+ return SANE_STATUS_INVAL;
+
+ if (info != NULL)
+ *info = 0;
+
+ switch (action)
+ {
+ case SANE_ACTION_GET_VALUE:
+ return (getvalue (handle, option, value));
+
+ case SANE_ACTION_SET_VALUE:
+ return (setvalue (handle, option, value, info));
+ default:
+ return SANE_STATUS_INVAL;
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+/* This function is part of the SANE API and gets called when the front end
+ * requests information aobut the scan configuration (e.g. color depth, mode,
+ * bytes and pixels per line, number of lines. This information is returned
+ * in the SANE_Parameters structure.
+ *
+ * Once a scan was started, this routine has to report the correct values, if
+ * it is called before the scan is actually started, the values are based on
+ * the current settings.
+ *
+ */
+SANE_Status
+estimate_parameters (Epson_Scanner *s, SANE_Parameters * params)
+{
+ int zoom, max_x, max_y;
+ int bytes_per_pixel;
+
+ int x_dpi = 0;
+ int y_dpi = 0;
+
+ SANE_Int max_y_orig = -1;
+
+ log_call ();
+
+ memset (&s->raw.ctx, 0, sizeof (SANE_Parameters));
+
+ x_dpi = s->val[OPT_X_RESOLUTION].w;
+ y_dpi = s->val[OPT_Y_RESOLUTION].w;
+
+ zoom = s->val[OPT_ZOOM].w;
+
+ if (need_autocrop_override (s))
+ { /* yucky changes to be reverted below */
+ max_y_orig = s->hw->src->max_y;
+ ((extension *) s->hw->src)->max_y = autocrop_max_y (s->hw);
+ update_ranges (s->hw, s->hw->src);
+ s->val[OPT_BR_Y].w = s->hw->src->y_range.max;
+ }
+
+ calculate_scan_area_max (s, &max_x, &max_y);
+
+ s->raw.ctx.pixels_per_line =
+ SANE_UNFIX (s->val[OPT_BR_X].w -
+ s->val[OPT_TL_X].w) / MM_PER_INCH * x_dpi * zoom / 100;
+ s->raw.ctx.lines =
+ SANE_UNFIX (s->val[OPT_BR_Y].w -
+ s->val[OPT_TL_Y].w) / MM_PER_INCH * y_dpi * zoom / 100;
+
+ log_data ("max x:%d y:%d [in pixels]", max_x, max_y);
+
+ if (max_x != 0 && max_y != 0)
+ {
+ if (max_x < s->raw.ctx.pixels_per_line)
+ s->raw.ctx.pixels_per_line = max_x;
+ if (max_y < s->raw.ctx.lines)
+ s->raw.ctx.lines = max_y;
+ }
+
+ if (s->raw.ctx.pixels_per_line < 8)
+ s->raw.ctx.pixels_per_line = 8;
+ if (s->raw.ctx.lines < 1)
+ s->raw.ctx.lines = 1;
+
+ log_data ("Preview = %d", s->val[OPT_PREVIEW].w);
+ log_data ("X Resolution = %d", s->val[OPT_X_RESOLUTION].w);
+ log_data ("Y Resolution = %d", s->val[OPT_Y_RESOLUTION].w);
+
+ log_data ("Scan area: TL (%.2f, %.2f) -- BR (%.2f, %.2f) [in mm]",
+ SANE_UNFIX (s->val[OPT_TL_X].w),
+ SANE_UNFIX (s->val[OPT_TL_Y].w),
+ SANE_UNFIX (s->val[OPT_BR_X].w),
+ SANE_UNFIX (s->val[OPT_BR_Y].w));
+
+ /* Calculate bytes_per_pixel and bytes_per_line for any color
+ * depths. The default color depth is stored in mode_params.depth.
+ */
+ if (mode_params[s->val[OPT_MODE].w].depth == 1)
+ {
+ s->raw.ctx.depth = 1;
+ }
+ else
+ {
+ s->raw.ctx.depth = s->val[OPT_BIT_DEPTH].w;
+ }
+
+ if (s->raw.ctx.depth > 8)
+ {
+ /* The frontends can only handle 8 or 16 bits for gray or color -
+ * so if it's more than 8, it gets automatically set to 16. This
+ * works as long as EPSON does not come out with a scanner that
+ * can handle more than 16 bits per color channel.
+ */
+ s->raw.ctx.depth = 16;
+ }
+
+ bytes_per_pixel = s->raw.ctx.depth / 8; /* this works because it can only be set to 1, 8 or 16 */
+ if (s->raw.ctx.depth % 8) /* just in case ... */
+ {
+ bytes_per_pixel++;
+ }
+
+ /* All models require alignment on a multiple of 8 pixels per line.
+ However, some models require multiples of 32 pixels instead of 8
+ when scanning in monochrome mode.
+ We use the largest multiple that is not larger than the original
+ value.
+ */
+ s->raw.ctx.pixels_per_line &= ~7;
+ if (1 == s->raw.ctx.depth)
+ {
+ s->raw.ctx.pixels_per_line &= ~31;
+ }
+
+ s->raw.ctx.last_frame = SANE_TRUE;
+
+ if (mode_params[s->val[OPT_MODE].w].color)
+ {
+ s->raw.ctx.format = SANE_FRAME_RGB;
+ s->raw.ctx.bytes_per_line =
+ 3 * s->raw.ctx.pixels_per_line * bytes_per_pixel;
+ }
+ else
+ {
+ s->raw.ctx.format = SANE_FRAME_GRAY;
+ s->raw.ctx.bytes_per_line =
+ s->raw.ctx.pixels_per_line * s->raw.ctx.depth / 8;
+ }
+
+ if (NULL != params)
+ memcpy (params, &s->raw.ctx, sizeof (SANE_Parameters));
+
+ print_params (s->raw.ctx);
+
+ if (need_autocrop_override (s))
+ { /* revert yucky changes made above */
+ ((extension *) s->hw->src)->max_y = max_y_orig;
+ update_ranges (s->hw, s->hw->src);
+ s->val[OPT_BR_Y].w = s->hw->src->y_range.max;
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+static SANE_Status
+device_init (Epson_Scanner *s)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ log_call ();
+
+ status = initialize (s->hw);
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+
+/* There is some undocumented special behavior with the TPU enable/disable.
+ * TPU power ESC e status
+ * on 0 NAK
+ * on 1 ACK
+ * off 0 ACK
+ * off 1 NAK
+ *
+ * It makes no sense to scan with TPU powered on and source flatbed, because
+ * light will come from both sides.
+ */
+
+ if (s->hw->adf || s->hw->tpu)
+ {
+ /* if it is previewing now, disable duplex */
+ SANE_Bool adf_duplex = ((1 == s->val[OPT_ADF_MODE].w)
+ && !s->val[OPT_PREVIEW].b);
+ status = control_option_unit (s->hw, adf_duplex);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ if (s->hw->tpu)
+ err_major ("You may have to power %s your TPU",
+ (using (s->hw, tpu) ? "on" : "off"));
+
+ err_major ("You may have to restart the SANE frontend.");
+ return status;
+ }
+
+ if (s->hw->cmd->request_extended_status != 0)
+ {
+ status = check_ext_status (s->hw);
+
+ if (SANE_STATUS_GOOD != status && SANE_STATUS_DEVICE_BUSY != status)
+ {
+ return status;
+ }
+ }
+ }
+
+ if (s->hw->using_fs)
+ {
+ cmd_request_scanning_parameter (s->hw);
+ s->hw->param_buf[39] = s->val[OPT_ADF_DFD_SENSITIVITY].w;
+ s->hw->param_buf[40] = (s->val[OPT_ADF_AUTO_SCAN].w ? 0xFF : 0x00);
+ }
+
+ return status;
+}
+
+static SANE_Status
+device_set_focus (Epson_Scanner *s)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ log_call ();
+
+ if (!s->hw->tpu || !using (s->hw, tpu)) return status;
+
+ /* set the focus position according to the extension used:
+ * if the TPU is selected, then focus 2.5mm above the glass,
+ * otherwise focus on the glass. Scanners that don't support
+ * this feature, will just ignore these calls.
+ */
+
+ if (s->hw->adf || s->hw->tpu)
+ {
+ if (s->hw->tpu && s->hw->tpu->has_focus)
+ {
+ if (s->val[OPT_FOCUS].w == 0)
+ {
+ log_info ("Setting focus to glass surface");
+ status = set_focus_position (s->hw, 0x40);
+ }
+ else
+ {
+ log_info ("Setting focus to 2.5mm above glass");
+ status = set_focus_position (s->hw, 0x59);
+ }
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+ }
+ }
+ return status;
+}
+
+static SANE_Status
+set_scan_parameters (Epson_Scanner *s, int *x_res, int *y_res)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const struct mode_param *mparam;
+ int x_dpi, y_dpi;
+
+ log_call ();
+
+ mparam = mode_params + s->val[OPT_MODE].w;
+ log_data ("setting data format to %d bits", mparam->depth);
+ status = set_data_format (s->hw, mparam->depth);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_data_format failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+
+ /* The byte sequence mode was introduced in B5, for B[34] we need
+ line sequence mode
+ */
+ if ((s->hw->cmd->level[0] == 'D' ||
+ (s->hw->cmd->level[0] == 'B' && s->hw->level >= 5)) &&
+ mparam->mode_flags == 0x02)
+ {
+ status = set_color_mode (s->hw, 0x13);
+ }
+ else
+ {
+ status = set_color_mode (s->hw, (mparam->mode_flags
+ | (mparam->dropout_mask
+ & dropout_params[s->val[OPT_DROPOUT].w])));
+ }
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_color_mode failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+
+ if (s->hw->cmd->set_halftoning &&
+ SANE_OPTION_IS_ACTIVE (s->opt[OPT_HALFTONE].cap))
+ {
+ status = set_halftoning (s->hw, halftone_params[s->val[OPT_HALFTONE].w]);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_halftoning failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+ }
+
+ s->brightness = 0;
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_BRIGHTNESS].cap))
+ {
+ SANE_Option_Descriptor *sopt = &(s->opt[OPT_BRIGHTNESS_METHOD]);
+ Option_Value *sval = &(s->val[OPT_BRIGHTNESS_METHOD]);
+
+ if (0 == strcmp_c (brightness_method_list[0], /* "hardware" */
+ sopt->constraint.string_list[sval->w]))
+ {
+ status = set_bright (s->hw, s->val[OPT_BRIGHTNESS].w);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_bright failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+ }
+ else /* software emulation */
+ {
+ s->brightness = s->val[OPT_BRIGHTNESS].w;
+ s->brightness /= s->opt[OPT_BRIGHTNESS].constraint.range->max;
+ }
+ }
+
+ s->contrast = 0;
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_CONTRAST].cap))
+ {
+ s->contrast = s->val[OPT_CONTRAST].w;
+ s->contrast /= s->opt[OPT_CONTRAST].constraint.range->max;
+ }
+
+ s->lut = dip_destroy_LUT (s->dip, s->lut);
+ if (0 != s->brightness || 0 != s->contrast) /* non-linear LUT */
+ {
+ SANE_Option_Descriptor *sopt = &(s->opt[OPT_BRIGHTNESS_METHOD]);
+ Option_Value *sval = &(s->val[OPT_BRIGHTNESS_METHOD]);
+
+ if (0 == strcmp_c (brightness_method_list[2], /* "gimp" */
+ sopt->constraint.string_list[sval->w]))
+ s->lut = dip_gimp_BC_LUT (s->dip, s->raw.ctx.depth,
+ s->brightness, s->contrast);
+ else
+ /* We use "iscan" for contrast by default. If "hardware" was
+ * given then s->brightness is 0 and doesn't produce knock-on
+ * effects with this method.
+ */
+ s->lut = dip_iscan_BC_LUT (s->dip, s->raw.ctx.depth,
+ s->brightness, s->contrast);
+
+ if (!s->lut)
+ status = SANE_STATUS_NO_MEM;
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_contrast failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+ }
+
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_MIRROR].cap))
+ {
+ status = mirror_image (s->hw, mirror_params[s->val[OPT_MIRROR].w]);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("mirror_image failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+ }
+
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_SPEED].cap))
+ {
+
+ if (s->val[OPT_PREVIEW].w)
+ status = set_speed (s->hw, speed_params[s->val[OPT_PREVIEW_SPEED].w]);
+ else
+ status = set_speed (s->hw, speed_params[s->val[OPT_SPEED].w]);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_speed failed (%s)", sane_strstatus (status));
+ return status;
+ }
+ }
+
+ /* use of speed_params is ok here since they are false and true.
+ * NOTE: I think I should throw that "params" stuff as long w is
+ * already the value.
+ */
+
+ s->invert_image = SANE_FALSE; /* default: to not inverting the image */
+
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_FILM_TYPE].cap))
+ {
+ status = set_film_type (s->hw, film_params[s->val[OPT_FILM_TYPE].w]);
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_film_type failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+ }
+
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_BAY].cap))
+ {
+ status = set_bay (s->hw, s->val[OPT_BAY].w);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_bay failed (%s)", sane_strstatus (status));
+ return status;
+ }
+ }
+
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_SHARPNESS].cap))
+ {
+
+ status = set_outline_emphasis (s->hw, s->val[OPT_SHARPNESS].w);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_outline_emphasis failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+ }
+
+ if (s->hw->cmd->set_gamma &&
+ SANE_OPTION_IS_ACTIVE (s->opt[OPT_GAMMA_CORRECTION].cap))
+ {
+ int val;
+ if (s->hw->cmd->level[0] == 'D')
+ {
+ /* The D1 level has only the two user defined gamma settings.
+ */
+ val = s->hw->gamma_type[s->val[OPT_GAMMA_CORRECTION].w];
+ }
+ else
+ {
+ val = s->hw->gamma_type[s->val[OPT_GAMMA_CORRECTION].w];
+
+ /* If "Default" is selected then determine the actual value to
+ * send to the scanner: If bilevel mode, just send the value
+ * from the table (0x01), for grayscale or color mode add one
+ * and send 0x02.
+ */
+ if (s->val[OPT_GAMMA_CORRECTION].w == 0)
+ {
+ val += mparam->depth == 1 ? 0 : 1;
+ }
+ }
+
+ log_info ("set_gamma (s, 0x%x)", val);
+ status = set_gamma (s->hw, val);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_gamma failed (%s)", sane_strstatus (status));
+ return status;
+ }
+ }
+
+ if (s->hw->cmd->set_gamma_table &&
+ s->hw->gamma_user_defined[s->val[OPT_GAMMA_CORRECTION].w])
+ { /* user defined. */
+ status = set_gamma_table (s->hw, s);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_gamma_table failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+ }
+
+ if (s->hw->cmd->set_color_correction)
+ {
+ int val = s->hw->color_type[s->val[OPT_COLOR_CORRECTION].w];
+
+ log_data ("set_color_correction (s, 0x%x)", val);
+ status = set_color_correction (s->hw, val);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_color_correction failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+ }
+
+ if (s->hw->color_user_defined[s->val[OPT_COLOR_CORRECTION].w])
+ {
+ size_t i;
+
+ status = set_color_correction_coefficients (s->hw, s);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_color_correction_coefficients failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+
+ log_info ("Color correction coefficients");
+ for (i = 0; i < 9; ++i)
+ log_info ("cct[%zd] = %f", i, s->cct[i]);
+ }
+
+ if (s->hw->cmd->set_threshold != 0
+ && SANE_OPTION_IS_ACTIVE (s->opt[OPT_THRESHOLD].cap))
+ {
+ status = set_threshold (s->hw, s->val[OPT_THRESHOLD].w);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_threshold failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+ }
+
+ x_dpi = s->val[OPT_X_RESOLUTION].w;
+ y_dpi = s->val[OPT_Y_RESOLUTION].w;
+
+ if (s->hw->using_fs)
+ status = dev_set_scanning_resolution (s->hw, x_dpi, y_dpi);
+ else
+ status = set_resolution (s->hw, x_dpi, y_dpi);
+
+ *x_res = x_dpi;
+ *y_res = y_dpi;
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_resolution(%d, %d) failed (%s)",
+ x_dpi, y_dpi, sane_strstatus (status));
+ return status;
+ }
+
+ if (s->hw->cmd->set_zoom != 0)
+ {
+ status = set_zoom (s->hw, s->val[OPT_ZOOM].w, s->val[OPT_ZOOM].w);
+ if (status != SANE_STATUS_GOOD)
+ {
+ err_fatal ("set_zoom(%d) failed (%s)",
+ s->val[OPT_ZOOM].w, sane_strstatus (status));
+ return status;
+ }
+ }
+ return status;
+}
+
+static void
+wait_for_button (Epson_Scanner *s)
+{
+ SANE_Bool button_status;
+
+ log_call ();
+
+ /* If WAIT_FOR_BUTTON is active, then do just that: Wait until the
+ * button is pressed. If the button was already pressed, then we
+ * will get the button Pressed event right away.
+ */
+ if (s->val[OPT_WAIT_FOR_BUTTON].w == SANE_TRUE)
+ {
+ s->hw->wait_for_button = SANE_TRUE;
+
+ while (s->hw->wait_for_button == SANE_TRUE)
+ {
+ if (s->raw.cancel_requested)
+ {
+ s->hw->wait_for_button = SANE_FALSE;
+ }
+ /* get the button status from the scanner */
+ else if (get_push_button_status (s->hw, &button_status) ==
+ SANE_STATUS_GOOD)
+ {
+ if (button_status == SANE_TRUE)
+ {
+ s->hw->wait_for_button = SANE_FALSE;
+ }
+ else
+ {
+ microsleep (s->hw->polling_time);
+ }
+ }
+ else
+ {
+ /* we run into an eror condition, just continue */
+ s->hw->wait_for_button = SANE_FALSE;
+ }
+ }
+ }
+}
+
+static SANE_Status
+set_line_count (Epson_Scanner *s)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ int lcount = 1;
+ s->hw->block_mode = SANE_FALSE;
+
+ log_call ();
+
+ /* The set line count commands needs to be sent for certain scanners
+ * in color mode. The D1 level requires it, we are however only
+ * testing for 'D' and not for the actual numeric level.
+ */
+ if (((s->hw->cmd->level[0] == 'B') &&
+ ((s->hw->level >= 5) || ((s->hw->level >= 4) &&
+ (!mode_params[s->val[OPT_MODE].w].color))))
+ || (s->hw->cmd->level[0] == 'D'))
+ {
+ channel *ch = s->hw->channel; /* for the sake of brevity */
+ s->hw->block_mode = SANE_TRUE;
+ lcount = ch->max_request_size (ch) / s->raw.ctx.bytes_per_line;
+
+ if (0 >= lcount) lcount = 1;
+
+ if (lcount > 255)
+ {
+ lcount = 255;
+ }
+
+ if (using (s->hw, tpu) && lcount > 32)
+ {
+ lcount = 32;
+ }
+
+ /* The D1 series of scanners only allow an even line number
+ * for bi-level scanning. If a bit depth of 1 is selected, then
+ * make sure the next lower even number is selected.
+ */
+ {
+ if (3 < lcount && lcount % 2)
+ {
+ lcount -= 1;
+ }
+ }
+ s->line_count = lcount;
+
+ if (lcount == 0)
+ {
+ err_fatal ("can not set zero line count");
+ return SANE_STATUS_NO_MEM;
+ }
+
+ status = set_lcount (s->hw, lcount);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_lcount(%d) failed (%s)",
+ lcount, sane_strstatus (status));
+ return status;
+ }
+ }
+ return status;
+}
+
+static SANE_Status
+request_command_parameters (Epson_Scanner *s)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ log_call ();
+
+ if (s->hw->cmd->request_condition != 0)
+ {
+ u_char params[2];
+ u_char result[4];
+ u_char *buf;
+ size_t len;
+
+ params[0] = ESC;
+ params[1] = s->hw->cmd->request_condition;
+
+ /* send request condition */
+ channel_send (s->hw->channel, params, 2, &status);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+
+ len = 4;
+ channel_recv (s->hw->channel, result, len, &status);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+ s->hw->status = result[1];
+
+ len = result[3] << 8 | result[2];
+ buf = t_alloca (len, u_char);
+ channel_recv (s->hw->channel, buf, len, &status);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+
+ log_info ("SANE_START: Color: %d", (int) buf[1]);
+ log_info ("SANE_START: Resolution (x, y): (%d, %d)",
+ (int) (buf[4] << 8 | buf[3]), (int) (buf[6] << 8 | buf[5]));
+ log_info ("SANE_START: Scan offset (x, y): (%d, %d)",
+ (int) (buf[9] << 8 | buf[8]), (int) (buf[11] << 8 | buf[10]));
+ log_info ("SANE_START: Scan size (w, h): (%d, %d)",
+ (int) (buf[13] << 8 | buf[12]), (int) (buf[15] << 8 | buf[14]));
+ log_info ("SANE_START: Data format: %d", (int) buf[17]);
+ log_info ("SANE_START: Halftone: %d", (int) buf[19]);
+ log_info ("SANE_START: Brightness: %d", (int) buf[21]);
+ log_info ("SANE_START: Gamma: %d", (int) buf[23]);
+ log_info ("SANE_START: Zoom (x, y): (%d, %d)", (int) buf[26],
+ (int) buf[25]);
+ log_info ("SANE_START: Color correction: %d", (int) buf[28]);
+ log_info ("SANE_START: Sharpness control: %d", (int) buf[30]);
+ log_info ("SANE_START: Scanning mode: %d", (int) buf[32]);
+ log_info ("SANE_START: Mirroring: %d", (int) buf[34]);
+ log_info ("SANE_START: Auto area segmentation: %d", (int) buf[36]);
+ log_info ("SANE_START: Threshold: %d", (int) buf[38]);
+ log_info ("SANE_START: Line counter: %d", (int) buf[40]);
+ log_info ("SANE_START: Option unit control: %d", (int) buf[42]);
+ log_info ("SANE_START: Film type: %d", (int) buf[44]);
+ }
+ return status;
+}
+
+static SANE_Status
+device_sheet_setup (Epson_Scanner *s)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ int left, top;
+
+ int x_dpi = 0;
+ int y_dpi = 0;
+
+ const struct mode_param *mparam = NULL;
+
+ status = dev_load_paper (s->hw);
+ if (SANE_STATUS_GOOD != status && SANE_STATUS_DEVICE_BUSY != status)
+ {
+ return status;
+ }
+ status = device_set_focus (s);
+ if (SANE_STATUS_GOOD != status && SANE_STATUS_DEVICE_BUSY != status)
+ {
+ return status;
+ }
+ status = dev_request_extended_status (s->hw);
+ if (SANE_STATUS_GOOD != status && SANE_STATUS_DEVICE_BUSY != status)
+ {
+ return status;
+ }
+
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_AUTOCROP].cap)
+ && s->val[OPT_AUTOCROP].b)
+ {
+ s->val[OPT_TL_X].w = SANE_FIX (0);
+ s->val[OPT_TL_Y].w = SANE_FIX (0);
+ s->val[OPT_BR_X].w = s->hw->src->x_range.max;
+ s->val[OPT_BR_Y].w = s->hw->src->y_range.max;
+ }
+ else if (has_size_check_support (s->hw->src)
+ && SANE_OPTION_IS_ACTIVE (s->opt[OPT_DETECT_DOC_SIZE].cap)
+ && s->val[OPT_DETECT_DOC_SIZE].w)
+ {
+ s->val[OPT_TL_X].w = SANE_FIX (0);
+ s->val[OPT_TL_Y].w = SANE_FIX (0);
+ s->val[OPT_BR_X].w = SANE_FIX (s->hw->src->doc_x);
+ s->val[OPT_BR_Y].w = SANE_FIX (s->hw->src->doc_y);
+ }
+
+ adf_handle_adjust_alignment (s, SANE_TRUE);
+
+ status = set_scan_parameters (s, &x_dpi, &y_dpi);
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+
+ calculate_scan_area_offset (s->val, &left, &top);
+
+ /* Calculate correction for line_distance in D1 scanner: Start
+ * line_distance lines earlier and add line_distance lines at the
+ * end.
+ *
+ * Because the actual line_distance is not yet calculated we have to
+ * do this first.
+ */
+
+ s->hw->color_shuffle = SANE_FALSE;
+ s->current_output_line = 0;
+ s->color_shuffle_line = 0;
+
+ mparam = mode_params + s->val[OPT_MODE].w;
+ if ((s->hw->optical_res != 0) && (mparam->depth == 8)
+ && (mparam->mode_flags != 0))
+ {
+ s->line_distance = s->hw->max_line_distance * x_dpi / s->hw->optical_res;
+ if (s->line_distance != 0)
+ {
+ s->hw->color_shuffle = SANE_TRUE;
+ }
+ else
+ s->hw->color_shuffle = SANE_FALSE;
+ }
+
+ estimate_parameters (s, NULL);
+ { /* finalise the number of scanlines */
+ int lines = s->raw.ctx.lines;
+ int max_lines = lines;
+
+ if (!need_autocrop_override (s))
+ {
+ max_lines = (SANE_UNFIX(s->hw->src->y_range.max)
+ * s->val[OPT_Y_RESOLUTION].w * s->val[OPT_ZOOM].w / 100
+ / MM_PER_INCH) + 0.5;
+ }
+ /* add extra lines at the top and bottom to minimise the loss of
+ scanlines due to colour shuffling */
+ if (SANE_TRUE == s->hw->color_shuffle)
+ {
+ top -= 1 * s->line_distance;
+ lines += 2 * s->line_distance;
+ }
+
+ /* make sure values are within range
+ In the worst case, this chomps 2 * s->line_distance lines from
+ the area the user wanted to scan. C'est la vie. */
+ top = max (0, top);
+ lines = min (lines, max_lines - top);
+
+ if (s->hw->using_fs)
+ {
+ status = dev_set_scanning_area (s->hw, left, top,
+ s->raw.ctx.pixels_per_line, lines);
+ }
+ else
+ {
+ status = set_scan_area (s->hw, left, top,
+ s->raw.ctx.pixels_per_line, lines);
+ }
+
+ /* substract the additional lines needed for colour shuffling so
+ the frontend can know how many lines to expect */
+ if (SANE_TRUE == s->hw->color_shuffle)
+ {
+ lines -= 2 * s->line_distance;
+ }
+ s->raw.ctx.lines = lines;
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("set_scan_area failed (%s)",
+ sane_strstatus (status));
+ return status;
+ }
+ }
+
+ status = set_line_count (s);
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+
+ if (s->hw->using_fs)
+ {
+ /* if it is previewing now, disable duplex */
+ byte adf_duplex = ((1 == s->val[OPT_ADF_MODE].w)
+ && !s->val[OPT_PREVIEW].b) ? 0x01 : 0x00;
+ dev_set_option_unit (s->hw, adf_duplex);
+ status = cmd_set_scanning_parameter (s->hw);
+ }
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+
+ if (s->hw->using_fs)
+ {
+ status = cmd_request_scanning_parameter (s->hw);
+ dev_log_scanning_parameter (s->hw);
+ }
+ else
+ status = request_command_parameters (s);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+
+ if (s->hw->channel->interpreter)
+ {
+ status = s->hw->channel->interpreter->ftor1 (s->hw->channel, &s->raw.ctx,
+ mparam->depth, left, x_dpi,
+ s->hw->optical_res);
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+ }
+
+ wait_for_button (s);
+
+ return status;
+}
+
+/*! \brief Implements image data buffer resizing policy.
+ *
+ * The implementation basically reuses previously acquired image data
+ * acquisition buffers as much as possible. This mitigates the risk
+ * of a (consecutive) scan aborting someway halfway through because
+ * memory allocation failed.
+ */
+static
+bool
+resize_warranted (size_t needed, size_t capacity)
+{
+ return needed > capacity;
+}
+
+SANE_Status
+sane_start (SANE_Handle handle)
+{
+ Epson_Scanner *s = (Epson_Scanner *) handle;
+ SANE_Status status;
+ u_char params[4];
+ size_t len_raw;
+
+ log_call ();
+
+ s->raw.cancel_requested = false;
+ s->raw.all_data_fetched = false;
+ s->raw.transfer_started = false;
+ s->raw.transfer_stopped = false;
+
+ s->img.cancel_requested = false;
+ s->img.all_data_fetched = false;
+ s->img.transfer_started = false;
+ s->img.transfer_stopped = false;
+
+ s->src = &s->raw;
+
+ if (0 == s->frame_count)
+ {
+ /* Back up original SANE options.
+ * \todo string and word array have to copy individually.
+ */
+ memcpy (s->val_bak, s->val, (sizeof (s->val[0]) * NUM_OPTIONS));
+ }
+ else if (!s->val[OPT_ADF_AUTO_SCAN].w)
+ {
+ /* Restore original SANE options.
+ * \todo string and word array have to copy individually.
+ */
+ memcpy (s->val, s->val_bak, (sizeof (s->val[0]) * NUM_OPTIONS));
+ }
+
+ if (!scan_area_is_valid (s))
+ {
+ err_fatal ("The image data resulting from the combination of the "
+ "specified scan area and resolution is too large.");
+ return SANE_STATUS_INVAL;
+ }
+
+ /* for AFF devices, check paper status here instead of in scan_finish
+ * to avoid blocking there in dev_request_extended_status()
+ */
+ if (0 < s->frame_count
+ && using (s->hw, adf)
+ && adf_has_auto_form_feed (s->hw))
+ adf_handle_out_of_paper (s);
+
+ if (0 == s->frame_count)
+ {
+ status = device_init (s);
+ if (SANE_STATUS_GOOD != status && SANE_STATUS_DEVICE_BUSY != status)
+ {
+ return status;
+ }
+ }
+
+ if (ENABLE_TIMING && 0 == time_pass_count)
+ {
+ time_clear ();
+ time_stamp (time_scan, start);
+ }
+
+ if (!s->val[OPT_ADF_AUTO_SCAN].w || 0 == s->frame_count)
+ {
+ status = device_sheet_setup (s);
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+ }
+
+ if (SANE_STATUS_GOOD != (status = check_warmup (s->hw))
+ && !(SANE_STATUS_NO_DOCS == status
+ && using (s->hw, adf)))
+ {
+ return status;
+ }
+
+ status = dev_lock (s->hw);
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+
+ params[0] = ESC;
+ if (s->hw->using_fs) params[0] = FS;
+ params[1] = s->hw->cmd->start_scanning;
+
+ if (ENABLE_TIMING && TIME_PASS_MAX > time_pass_count)
+ time_stamp (time_pass[time_pass_count], start);
+
+ channel_send (s->hw->channel, params, 2, &status);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_fatal ("start failed (%s)", sane_strstatus (status));
+ return status;
+ }
+
+ if (s->hw->color_shuffle == SANE_TRUE)
+ {
+ size_t len_line_buffer = s->raw.ctx.bytes_per_line;
+
+ if (resize_warranted (len_line_buffer, s->cap_line_buffer))
+ {
+ size_t i;
+
+ for (i = 0; i < 2 * s->line_distance + 1; ++i)
+ delete (s->line_buffer[i]);
+ s->cap_line_buffer = 0;
+
+ for (i = 0; i < 2 * s->line_distance + 1; ++i)
+ {
+ SANE_Byte *p = t_malloc (len_line_buffer, SANE_Byte);
+
+ if (p)
+ {
+ s->line_buffer[i] = p;
+ }
+ else /* clean up and bail */
+ {
+ size_t j;
+
+ for (j = 0; j < i; ++i)
+ delete (s->line_buffer[i]);
+
+ s->cap_line_buffer = 0;
+
+ return SANE_STATUS_NO_MEM;
+ }
+ s->cap_line_buffer = len_line_buffer;
+ }
+ }
+ }
+
+ if (s->hw->using_fs)
+ {
+ s->hw->block_mode = SANE_TRUE;
+ status = read_image_info_block (s->hw);
+ s->raw.transfer_started = (s->hw->block_total
+ * s->hw->image_block_size
+ + s->hw->final_block_size
+ > 0);
+ if (SANE_STATUS_GOOD != status) return status;
+
+ len_raw = s->hw->image_block_size + 1; /* for error code */
+ }
+ else
+ {
+ len_raw = s->line_count * s->raw.ctx.bytes_per_line;
+ }
+
+ if (resize_warranted (len_raw, s->raw.cap))
+ {
+ delete (s->raw.buf);
+ s->raw.cap = 0;
+
+ if (!(s->raw.buf = t_malloc (len_raw, SANE_Byte)))
+ return SANE_STATUS_NO_MEM;
+
+ s->raw.cap = len_raw;
+ }
+ s->raw.ptr = s->raw.end = s->raw.buf;
+ s->raw.transfer_started = true;
+
+ /* This here will block sane_start() until the whole image has been
+ * scanned and pre-processed. The assumption made here is that the
+ * pre-processing can not be done in place and that the resulting
+ * image is no larger than the image acquired.
+ */
+ if (dip_needs_whole_image (s->dip, s->val, s->opt))
+ {
+ SANE_Int len_img = s->raw.ctx.bytes_per_line * s->raw.ctx.lines;
+ SANE_Int max = len_img;
+ SANE_Int len = 0;
+
+ log_info ("buffering image before returning from sane_start()");
+
+ if (resize_warranted (len_img, s->img.cap))
+ {
+ delete (s->img.buf);
+ s->img.cap = 0;
+
+ if (!(s->img.buf = t_malloc (len_img, SANE_Byte)))
+ return SANE_STATUS_NO_MEM;
+
+ s->img.cap = len_img;
+ }
+
+ s->img.ptr = s->img.buf;
+ do /* note: non-blocking I/O not supported */
+ {
+ s->img.ptr += len;
+ max -= len;
+ status = fetch_image_data (s, s->img.ptr, max, &len);
+ }
+ while (SANE_STATUS_GOOD == status);
+
+ if (SANE_STATUS_EOF != status)
+ return status;
+
+ if (0 != max)
+ return SANE_STATUS_IO_ERROR;
+
+ s->img.ptr = s->img.buf;
+ s->img.end = s->img.buf + len_img;
+ memcpy (&s->img.ctx, &s->raw.ctx, sizeof (s->raw.ctx));
+
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_DESKEW].cap)
+ && s->val[OPT_DESKEW].b)
+ {
+ dip_deskew (s->dip, s->hw, s->frame_count, &s->img, s->val);
+ }
+
+ if (SANE_OPTION_IS_ACTIVE (s->opt[OPT_AUTOCROP].cap)
+ && s->val[OPT_AUTOCROP].b)
+ {
+ dip_autocrop (s->dip, s->hw, s->frame_count, &s->img, s->val);
+ }
+
+ s->img.all_data_fetched = true;
+ s->img.transfer_started = true;
+ s->src = &s->img;
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+/*! Retrieves an image info block and computes image data block info.
+ */
+static SANE_Status
+read_image_info_block (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ u_char buf[14]; /* largest image handshake "info" block */
+ int buf_size = num_of (buf);
+
+ const int limit = 30;
+ int ticks = 0;
+
+ log_call ();
+
+ if (!hw->using_fs)
+ buf_size = (hw->block_mode ? 6 : 4);
+
+ channel_recv_all_retry (hw->channel, buf, buf_size,
+ MAX_READ_ATTEMPTS, &status);
+
+ /* Prevent reporting of stale values if we bail early.
+ */
+ hw->image_block_size = 0;
+ hw->final_block_size = 0;
+ if (hw->using_fs)
+ {
+ hw->block_total = 0;
+ hw->block_count = 0;
+ }
+
+ if (SANE_STATUS_GOOD != status)
+ return status;
+
+ if (STX != buf[0])
+ {
+ log_data ("code %02x", buf[0]);
+ log_data ("error, expected STX");
+
+ return SANE_STATUS_INVAL;
+ }
+ hw->status = buf[1];
+
+ /* Update values here so they are also available in case we bail in
+ the while loop below.
+ */
+ if (hw->using_fs)
+ {
+ hw->image_block_size = buf_to_uint32 (buf + 2);
+ hw->final_block_size = buf_to_uint32 (buf + 10);
+ hw->block_total = buf_to_uint32 (buf + 6);
+ hw->block_count = 0;
+ }
+ else
+ {
+ hw->image_block_size = buf[3] << 8 | buf[2];
+ if (hw->block_mode)
+ hw->image_block_size *= buf[5] << 8 | buf[4];
+ }
+
+ /* Although spec compliant scanners have their warming up bit set
+ when they are getting ready for a scan, the world is less than
+ perfect :-(
+
+ We hack around non-compliant behaviour by looping until 'ESC G'
+ succeeds or our patience runs out. In the latter case we just
+ claim that the device is busy.
+
+ However, when the ADF function is used, and the last paper is
+ scanned, we do not need looping any more.
+ */
+ while (hw->status & (STATUS_FATAL_ERROR | STATUS_NOT_READY)
+ && !(using (hw, adf)
+ && (hw->status & STATUS_AREA_END))
+ && ticks++ < limit)
+ {
+ u_char cmd[2];
+
+ err_fatal ("fatal error - Status = %02x", hw->status);
+
+ if (SANE_STATUS_GOOD != (status = check_warmup (hw)))
+ return status;
+
+ cmd[0] = (hw->using_fs ? FS : ESC);
+ cmd[1] = 'G';
+
+ channel_send (hw->channel, cmd, 2, &status);
+ sleep (1);
+ channel_recv_all_retry (hw->channel, buf, buf_size,
+ MAX_READ_ATTEMPTS, &status);
+ hw->status = buf[1];
+
+ if (hw->using_fs)
+ {
+ hw->image_block_size = buf_to_uint32 (buf + 2);
+ hw->final_block_size = buf_to_uint32 (buf + 10);
+ hw->block_total = buf_to_uint32 (buf + 6);
+ hw->block_count = 0;
+ }
+ else
+ {
+ hw->image_block_size = buf[3] << 8 | buf[2];
+ if (hw->block_mode)
+ hw->image_block_size *= buf[5] << 8 | buf[4];
+ }
+ }
+ return (ticks < limit
+ ? status
+ : SANE_STATUS_DEVICE_BUSY);
+}
+
+
+static void
+scan_finish (Epson_Scanner * s)
+{
+ log_call ();
+
+ s->raw.transfer_stopped = true;
+
+ if (s->hw->channel->interpreter) /* FIXME: do we really need this? */
+ {
+ s->hw->channel->interpreter->free (s->hw->channel);
+ }
+
+ s->frame_count++;
+
+ if (!using (s->hw, adf)) return; /* we're done */
+
+ if (!(EXT_STATUS_ADFT & s->hw->ext_status) /* sheet feed */
+ && !adf_has_auto_form_feed (s->hw))
+ {
+ dev_eject_paper (s->hw);
+ }
+
+ /* avoid blocking here on dev_request_extended_status()
+ * with AFF devices, unless canceling
+ */
+ if (!adf_has_auto_form_feed (s->hw) || s->raw.cancel_requested)
+ adf_handle_out_of_paper (s);
+}
+
+/* part of the scan sequence logic and should not be used generally */
+static void
+adf_handle_out_of_paper (Epson_Scanner * s)
+{
+ if (!using (s->hw, adf)) return;
+
+ /* if we're finishing an ADF scan and the ADF unit has been disabled,
+ * enable it again in order to get accurate status information
+ */
+ if (using (s->hw, adf) && !(ADF_STATUS_EN & s->hw->adf->status))
+ {
+ /* if it is previewing now, disable duplex */
+ byte value = ((s->hw->adf->using_duplex && !s->val[OPT_PREVIEW].b)
+ ? 0x02 : 0x01);
+ cmd_control_option_unit (s->hw, value);
+ }
+
+ dev_request_extended_status (s->hw);
+ if (!(ADF_STATUS_PE & s->hw->adf->status)) return;
+
+ log_info ("ADF: out of paper, %s mode, %d sheets",
+ ((ADF_STATUS_PAG & s->hw->adf->status) ? "duplex" : "simplex"),
+ s->hw->adf->sheet_count);
+ if (!(ADF_STATUS_PAG & s->hw->adf->status)
+ || 0 == (s->hw->adf->sheet_count % 2))
+ {
+ dev_eject_paper (s->hw);
+ s->frame_count = 0;
+ /* Restore original SANE options.
+ * \todo string and word array have to copy individually.
+ */
+ memcpy (s->val, s->val_bak, (sizeof (s->val[0]) * NUM_OPTIONS));
+ }
+ else
+ log_info ("ADF: scanning reverse side");
+}
+
+static SANE_Bool
+adf_needs_realignment (const device *hw)
+{
+ SANE_Byte dpos;
+
+ require (using (hw, adf));
+
+ dpos = hw->fsi_cap_3 & FSI_CAP_DPOS_MASK;
+
+ return (adf_needs_manual_centering (hw)
+ || FSI_CAP_DPOS_CNTR == dpos
+ || FSI_CAP_DPOS_RIGT == dpos);
+}
+
+static void
+adf_handle_adjust_alignment (Epson_Scanner *s, SANE_Bool finalize)
+{
+ scan_area_t adf_scan_area;
+ double shift = 0.0;
+ double sides = 2; /* distribute shift on both sides
+ --> we center by default */
+ log_call();
+
+ if (!using (s->hw, adf)) return;
+ if (!adf_needs_realignment (s->hw)) return;
+
+ log_info ("before alignment: tl-x = %.2f, br-x = %.2f",
+ SANE_UNFIX (s->val[OPT_TL_X].w),
+ SANE_UNFIX (s->val[OPT_BR_X].w));
+
+ adf_scan_area =
+ get_model_info_max_scan_area (s->hw, s->val[OPT_ADF_MODE].w);
+
+ if(SANE_UNFIX (adf_scan_area.width) < 0
+ && SANE_UNFIX (adf_scan_area.height) < 0)
+ {
+ adf_scan_area.width = s->hw->src->x_range.max;
+ adf_scan_area.height = s->hw->src->y_range.max;
+ }
+
+ if (FSI_CAP_DPOS_RIGT == s->hw->fsi_cap_3 & FSI_CAP_DPOS_MASK)
+ {
+ sides = 1; /* put whole shift on one side */
+ }
+
+ /* scan area setting or no marquee */
+ if (!finalize ||
+ (adf_scan_area.width == (s->val[OPT_BR_X].w - s->val[OPT_TL_X].w)
+ && adf_scan_area.height == (s->val[OPT_BR_Y].w - s->val[OPT_TL_Y].w)))
+ {
+ double scan_width = (SANE_UNFIX (s->val[OPT_BR_X].w)
+ - SANE_UNFIX (s->val[OPT_TL_X].w));
+ shift = SANE_UNFIX (s->hw->src->x_range.max) - scan_width;
+ shift /= sides;
+
+ s->val[OPT_TL_X].w = SANE_FIX (shift + 0);
+ s->val[OPT_BR_X].w = SANE_FIX (shift + scan_width);
+ }
+ /* uses auto-detect document size */
+ else if (has_size_check_support (s->hw->src)
+ && SANE_OPTION_IS_ACTIVE (s->opt[OPT_DETECT_DOC_SIZE].cap)
+ && s->val[OPT_DETECT_DOC_SIZE].w)
+ {
+ shift = SANE_UNFIX (s->hw->src->x_range.max) - s->hw->src->doc_x;
+ shift /= sides;
+
+ s->val[OPT_TL_X].w = SANE_FIX (shift + 0);
+ s->val[OPT_BR_X].w = SANE_FIX (shift + s->hw->src->doc_x);
+ }
+ /* has marquee and adf-duplex scan area differ from adf-simplex scan area */
+ else if (!(adf_scan_area.width == s->hw->src->x_range.max
+ && adf_scan_area.height == s->hw->src->y_range.max))
+ {
+ shift = SANE_UNFIX (s->hw->src->x_range.max - adf_scan_area.width);
+ shift /= sides;
+
+ s->val[OPT_TL_X].w += SANE_FIX (shift);
+ s->val[OPT_BR_X].w += SANE_FIX (shift);
+ }
+
+ log_info ("after alignment : tl-x = %.2f, br-x = %.2f",
+ SANE_UNFIX (s->val[OPT_TL_X].w),
+ SANE_UNFIX (s->val[OPT_BR_X].w));
+ log_info ("shifted scan area offset by %.2f mm", shift);
+}
+
+#define GET_COLOR(x) (((x) >> 2) & 0x03)
+
+SANE_Status
+fetch_image_data (Epson_Scanner *s, SANE_Byte * data, SANE_Int max_length,
+ SANE_Int * length)
+{
+ SANE_Status status;
+ int index = 0;
+ SANE_Bool reorder = SANE_FALSE;
+ SANE_Bool needStrangeReorder = SANE_FALSE;
+
+ log_call ();
+
+ if (s->raw.transfer_stopped && s->raw.cancel_requested)
+ return SANE_STATUS_CANCELLED;
+
+START_READ:
+ if (s->raw.ptr == s->raw.end)
+ {
+ size_t buf_len;
+
+ if (s->raw.all_data_fetched)
+ {
+ *length = 0;
+ return SANE_STATUS_EOF;
+ }
+
+ if (!s->hw->using_fs)
+ {
+ status = read_image_info_block (s->hw);
+ if (SANE_STATUS_GOOD != status)
+ {
+ *length = 0;
+ scan_finish (s);
+ return status;
+ }
+ buf_len = s->hw->image_block_size;
+ }
+ else
+ {
+ buf_len = s->hw->image_block_size;
+ if (s->hw->block_count >= s->hw->block_total)
+ buf_len = s->hw->final_block_size;
+ ++buf_len; /* include error byte */
+ }
+
+ if (!s->hw->block_mode && SANE_FRAME_RGB == s->raw.ctx.format)
+ {
+ /* Read color data in line mode */
+
+ /* read the first color line - the number of bytes to read
+ * is already known (from last call to read_image_info_block()
+ * We determine where to write the line from the color information
+ * in the data block. At the end we want the order RGB, but the
+ * way the data is delivered does not guarantee this - actually it's
+ * most likely that the order is GRB if it's not RGB!
+ */
+ switch (GET_COLOR (s->hw->status))
+ {
+ case 1:
+ index = 1;
+ break;
+ case 2:
+ index = 0;
+ break;
+ case 3:
+ index = 2;
+ break;
+ }
+
+ channel_recv (s->hw->channel, s->raw.buf + index * s->raw.ctx.pixels_per_line,
+ buf_len, &status);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ *length = 0;
+ scan_finish (s);
+ return status;
+ }
+
+ /* send the ACK signal to the scanner in order to make
+ * it ready for the next image info block.
+ */
+ channel_send (s->hw->channel, S_ACK, 1, &status);
+
+ /* ... and request the next image info block
+ */
+ if (SANE_STATUS_GOOD != (status = read_image_info_block (s->hw)))
+ {
+ *length = 0;
+ scan_finish (s);
+ return status;
+ }
+
+ buf_len = s->hw->image_block_size;
+
+ switch (GET_COLOR (s->hw->status))
+ {
+ case 1:
+ index = 1;
+ break;
+ case 2:
+ index = 0;
+ break;
+ case 3:
+ index = 2;
+ break;
+ }
+
+ channel_recv (s->hw->channel, s->raw.buf + index * s->raw.ctx.pixels_per_line,
+ buf_len, &status);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ *length = 0;
+ scan_finish (s);
+ return status;
+ }
+
+ channel_send (s->hw->channel, S_ACK, 1, &status);
+
+ /* ... and the last image info block
+ */
+ if (SANE_STATUS_GOOD != (status = read_image_info_block (s->hw)))
+ {
+ *length = 0;
+ scan_finish (s);
+ return status;
+ }
+
+ buf_len = s->hw->image_block_size;
+
+ switch (GET_COLOR (s->hw->status))
+ {
+ case 1:
+ index = 1;
+ break;
+ case 2:
+ index = 0;
+ break;
+ case 3:
+ index = 2;
+ break;
+ }
+
+ channel_recv (s->hw->channel, s->raw.buf + index * s->raw.ctx.pixels_per_line,
+ buf_len, &status);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ *length = 0;
+ scan_finish (s);
+ return status;
+ }
+ }
+ else
+ {
+ /* Read image data in block mode */
+
+ /* do we have to reorder the image data ? */
+ if (GET_COLOR (s->hw->status) == 0x01)
+ {
+ reorder = SANE_TRUE;
+ }
+
+ channel_recv_all_retry (s->hw->channel, s->raw.buf, buf_len,
+ MAX_READ_ATTEMPTS, &status);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ *length = 0;
+ scan_finish (s);
+ return status;
+ }
+ }
+
+ if (s->hw->using_fs)
+ {
+ u_char err = 0;
+
+ if (s->hw->block_count >= s->hw->block_total)
+ s->raw.all_data_fetched = true;
+ ++(s->hw->block_count);
+ log_info ("read image block %u/%u",
+ s->hw->block_count, s->hw->block_total + 1);
+
+ err = s->raw.buf[--buf_len]; /* drop error byte */
+ log_info ("image block error byte: %x", err);
+
+ if ((FSG_FATAL_ERROR | FSG_NOT_READY) & err)
+ {
+ *length = 0;
+ scan_finish (s);
+ return check_ext_status (s->hw);
+ }
+ else if (FSG_CANCEL_REQUEST & err)
+ {
+ s->raw.cancel_requested = true;
+ }
+ else if (FSG_PAGE_END & err)
+ {
+ if (FSI_CAP_PED & s->hw->fsi_cap_2)
+ {
+ log_info ("paper end flag raised");
+ }
+ else
+ {
+ err_minor ("invalid paper end flag raised");
+ }
+ }
+ else if (0 != err)
+ {
+ log_info ("unknown error flag(s) raised");
+ *length = 0;
+ scan_finish (s);
+ return check_ext_status (s->hw);
+ }
+ }
+ else
+ {
+ s->raw.all_data_fetched = (STATUS_AREA_END & s->hw->status);
+ }
+
+ if (s->raw.all_data_fetched
+ && ENABLE_TIMING && TIME_PASS_MAX > time_pass_count)
+ {
+ time_stamp (time_pass[time_pass_count], stop);
+ ++time_pass_count;
+ }
+
+ if (!s->raw.all_data_fetched)
+ {
+ if (s->raw.cancel_requested)
+ {
+ channel_send (s->hw->channel, S_CAN, 1, &status);
+ if (SANE_STATUS_GOOD != status) return status;
+
+ status = expect_ack (s->hw);
+ if (SANE_STATUS_GOOD != status) return status;
+
+ *length = 0;
+
+ scan_finish (s);
+
+ return SANE_STATUS_CANCELLED;
+ }
+ else
+ {
+ channel_send (s->hw->channel, S_ACK, 1, &status);
+ if (SANE_STATUS_GOOD != status) return status;
+ }
+ }
+
+ s->raw.end = s->raw.buf + buf_len;
+ s->raw.ptr = s->raw.buf;
+
+ /* if we have to re-order the color components (GRB->RGB) we
+ * are doing this here:
+ */
+
+ /* Some scanners (e.g. the Perfection 1640 and GT-2200) seem
+ * to have the R and G channels swapped.
+ * The GT-8700 is the Asian version of the Perfection1640.
+ * If the scanner name is one of these, and the scan mode is
+ * RGB then swap the colors.
+ */
+
+ needStrangeReorder = ((0 == strcmp_c (s->hw->fw_name, "GT-2200")
+ || 0 == strcmp_c (s->hw->fw_name, "Perfection1640")
+ || 0 == strcmp_c (s->hw->fw_name, "GT-8700"))
+ && s->raw.ctx.format == SANE_FRAME_RGB);
+
+ /* Certain Perfection 1650 also need this re-ordering of the two
+ * color channels. These scanners are identified by the problem
+ * with the half vertical scanning area. When we corrected this,
+ * we also set the variable s->hw->need_color_reorder
+ */
+ if (s->hw->need_color_reorder)
+ {
+ needStrangeReorder = SANE_TRUE;
+ }
+
+ if (needStrangeReorder)
+ reorder = SANE_FALSE; /* reordering once is enough */
+
+ if (s->raw.ctx.format != SANE_FRAME_RGB)
+ reorder = SANE_FALSE; /* don't reorder for BW or gray */
+
+ if (reorder)
+ {
+ s->raw.ptr = s->raw.buf;
+ dip_change_GRB_to_RGB (s->dip, &s->raw);
+ }
+
+ /* Do the color_shuffle if everything else is correct - at this
+ * time most of the stuff is hardcoded for the Perfection 610
+ */
+ if (s->hw->color_shuffle)
+ {
+ int new_length = 0;
+
+ status = color_shuffle (s, &new_length);
+
+ /* If no bytes are returned, check if the scanner is already
+ * done, if so, we'll probably just return, but if there is more
+ * data to process get the next batch.
+ */
+
+ if (new_length == 0 && s->raw.end != s->raw.ptr)
+ {
+ goto START_READ;
+ }
+
+ s->raw.end = s->raw.buf + new_length;
+ s->raw.ptr = s->raw.buf;
+ }
+
+ if ((SANE_CAP_EMULATED & s->opt[OPT_CCT_1].cap)
+ && s->hw->color_user_defined[s->val[OPT_COLOR_CORRECTION].w]
+ && SANE_FRAME_RGB == s->raw.ctx.format)
+ {
+ dip_apply_color_profile (s->dip, &s->raw, s->cct);
+ }
+
+ if (s->hw->channel->interpreter)
+ {
+ s->hw->channel->interpreter->ftor0 (s->hw->channel,
+ &s->raw.ctx,
+ s->raw.ptr, s->raw.end);
+ }
+
+ if (s->lut)
+ {
+ dip_apply_LUT (s->dip, &s->raw, s->lut);
+ }
+
+ /* WARNING: The SANE specification normally uses zero to indicate
+ * minimum intensity. However, SANE_FRAME_GRAY images with a bit
+ * depth of one use zero to indicate *maximum* intensity.
+ * The device always uses zero for minimum intensity, irrespective
+ * of the color mode and bit depth.
+ */
+ if (1 == s->raw.ctx.depth && SANE_FRAME_GRAY == s->raw.ctx.format)
+ {
+ if (!s->invert_image)
+ dip_flip_bits (s->dip, &s->raw);
+ }
+ else
+ {
+ if (s->invert_image)
+ dip_flip_bits (s->dip, &s->raw);
+ }
+ }
+
+ /* copy the image data to the data memory area
+ */
+ if (!s->hw->block_mode && SANE_FRAME_RGB == s->raw.ctx.format)
+ {
+ max_length /= 3;
+
+ if (max_length > s->raw.end - s->raw.ptr)
+ max_length = s->raw.end - s->raw.ptr;
+
+ *length = 3 * max_length;
+
+ if (s->raw.cancel_requested)
+ s->raw.ptr += max_length;
+ else
+ {
+ while (max_length-- != 0)
+ {
+ *data++ = s->raw.ptr[0];
+ *data++ = s->raw.ptr[s->raw.ctx.pixels_per_line];
+ *data++ = s->raw.ptr[2 * s->raw.ctx.pixels_per_line];
+ ++s->raw.ptr;
+ }
+ }
+ }
+ else
+ {
+ if (max_length > s->raw.end - s->raw.ptr)
+ max_length = s->raw.end - s->raw.ptr;
+
+ *length = max_length;
+
+ if (s->raw.cancel_requested)
+ s->raw.ptr += max_length;
+ else
+ {
+ memcpy (data, s->raw.ptr, max_length);
+ s->raw.ptr += max_length;
+ }
+ }
+
+ if (s->raw.ptr == s->raw.end && s->raw.all_data_fetched)
+ {
+ scan_finish (s);
+ if (0 == strcmp_c (s->hw->fw_name, "DS-30"))
+ {
+ status = check_ext_status (s->hw);
+ /* Ignore ADF paper empty */
+ s->hw->adf->status &= ~ADF_STATUS_PE;
+ if ( SANE_STATUS_GOOD != status
+ && SANE_STATUS_NO_DOCS != status)
+ {
+ return status;
+ }
+ }
+ }
+ log_call ("exit");
+
+ return SANE_STATUS_GOOD;
+}
+
+
+/*! Puts raw scan data into the correct scan line.
+
+ When scanning with a non-zero line distance, the RGB data is \e
+ not on a single line in the raw scan data. The RGB channels are
+ separated by a number of lines that depends on the line distance
+ as reported by the ESC i command and the resolution of the scan.
+
+ This function reorganises raw scan data so that the RGB channels
+ are no longer separated and all data is on the same scan line.
+
+ \note
+ It seems that the Perfection 610 and 640U both report a maximum
+ scan area with the two line distances included (based on 11.7"
+ at 600dpi = 7020, whereas the scan area is reportedly 7036 with
+ both line distances equal to 8).
+ */
+static SANE_Status
+color_shuffle (Epson_Scanner *s, int *new_length)
+{
+ SANE_Byte *buf = s->raw.buf;
+ int length = s->raw.end - s->raw.buf;
+
+ log_call ();
+
+ if (s->hw->color_shuffle == SANE_TRUE)
+ {
+ SANE_Byte *data_ptr; /* ptr to data to process */
+ SANE_Byte *data_end; /* ptr to end of processed data */
+ SANE_Byte *out_data_ptr; /* ptr to memory when writing data */
+ int i; /* loop counter */
+
+ log_call ();
+
+ /* Initialize the variables we are going to use for the
+ * copying of the data. data_ptr is the pointer to
+ * the currently worked on scan line. data_end is the
+ * end of the data area as calculated from adding *length
+ * to the start of data.
+ * out_data_ptr is used when writing out the processed data
+ * and always points to the beginning of the next line to
+ * write.
+ */
+
+ data_ptr = out_data_ptr = buf;
+ data_end = data_ptr + length;
+
+ /* The image data is in *raw.buf, we know that the buffer contains
+ * s->raw.end - s->raw.buf ( = length) bytes of data. The width of
+ * one line is in s->raw.ctx.bytes_per_line
+ */
+
+ /* The buffer area is supposed to have a number of full scan
+ * lines, let's test if this is the case.
+ */
+
+ if (length % s->raw.ctx.bytes_per_line != 0)
+ {
+ err_major ("ERROR in size of buffer: %d / %d",
+ length, s->raw.ctx.bytes_per_line);
+ return SANE_STATUS_INVAL;
+ }
+
+ while (data_ptr < data_end)
+ {
+ SANE_Byte *source_ptr, *dest_ptr;
+ int loop;
+
+ /* copy the green information into the current line */
+
+ source_ptr = data_ptr + 1;
+ dest_ptr = s->line_buffer[s->color_shuffle_line] + 1;
+
+ for (i = 0; i < s->raw.ctx.bytes_per_line / 3; i++)
+ {
+ *dest_ptr = *source_ptr;
+ dest_ptr += 3;
+ source_ptr += 3;
+ }
+
+ /* copy the red information n lines back */
+
+ if (s->color_shuffle_line >= s->line_distance)
+ {
+ source_ptr = data_ptr + 2;
+ dest_ptr =
+ s->line_buffer[s->color_shuffle_line - s->line_distance] + 2;
+
+ for (loop = 0; loop < s->raw.ctx.bytes_per_line / 3; loop++)
+ {
+ *dest_ptr = *source_ptr;
+ dest_ptr += 3;
+ source_ptr += 3;
+ }
+ }
+
+ /* copy the blue information n lines forward */
+
+ source_ptr = data_ptr;
+ dest_ptr = s->line_buffer[s->color_shuffle_line + s->line_distance];
+
+ for (loop = 0; loop < s->raw.ctx.bytes_per_line / 3; loop++)
+ {
+ *dest_ptr = *source_ptr;
+ dest_ptr += 3;
+ source_ptr += 3;
+ }
+
+ data_ptr += s->raw.ctx.bytes_per_line;
+ s->raw.ptr += s->raw.ctx.bytes_per_line;
+
+ if (s->color_shuffle_line == s->line_distance)
+ {
+ /* We just finished shuffling the line in line_buffer[0] -
+ * write the RGB image data in it to the output buffer and
+ * cyclically shift the line_buffers up by one.
+ */
+
+ SANE_Byte *first;
+
+ if ((s->current_output_line >= s->line_distance) &&
+ (s->current_output_line < s->raw.ctx.lines + s->line_distance))
+ {
+ memcpy (out_data_ptr, s->line_buffer[0], s->raw.ctx.bytes_per_line);
+ out_data_ptr += s->raw.ctx.bytes_per_line;
+ }
+
+ s->current_output_line++;
+
+ first = s->line_buffer[0];
+ for (i = 0; i < 2 * s->line_distance; ++i)
+ {
+ s->line_buffer[i] = s->line_buffer[i + 1];
+ }
+ s->line_buffer[2 * s->line_distance] = first;
+ }
+ else
+ {
+ s->color_shuffle_line++; /* increase the buffer number */
+ }
+ }
+
+ /* At this time we've used up all the new data from the scanner,
+ * some of it is still in the line_buffers, but we are ready to
+ * return some of it to the front end software. To do so we have
+ * to adjust the size of the data area and the *new_length
+ * variable.
+ */
+
+ *new_length = out_data_ptr - buf;
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+
+static SANE_Status
+get_identity_information (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ log_call ();
+
+ if (!hw->cmd->request_identity)
+ return SANE_STATUS_INVAL;
+
+ status = cmd_request_identity (hw);
+ if (SANE_STATUS_GOOD != status)
+ {
+ return SANE_STATUS_INVAL;
+ }
+ log_data ("detected command level %s", hw->cmd_lvl);
+
+ {
+ char *force = getenv ("SANE_EPSON_CMD_LVL");
+
+ if (force)
+ {
+ hw->cmd_lvl[0] = force[0];
+ hw->cmd_lvl[1] = force[1];
+ log_info ("forced command level %s", hw->cmd_lvl);
+ }
+ }
+
+ /* check if option equipment is installed */
+
+ if (hw->status & STATUS_OPTION)
+ {
+ log_info ("option equipment is installed");
+ }
+
+ delete (hw->tpu); /* FIXME: is this necessary?? */
+ delete (hw->adf);
+
+ /* set command type and level */
+ {
+ int n = 0;
+
+ while (num_of (epson_cmd) > n
+ && (0 != strcmp_c (hw->cmd_lvl, epson_cmd[n].level)))
+ ++n;
+
+ if (num_of (epson_cmd) > n)
+ hw->cmd = &epson_cmd[n];
+ else
+ err_minor ("unknown command level %s, using %s instead",
+ hw->cmd_lvl, hw->cmd->level);
+
+ hw->level = hw->cmd->level[1] - '0';
+ } /* set comand type and level */
+
+ if (hw->using_fs && 'D' != hw->cmd->level[0]) return SANE_STATUS_GOOD;
+
+ /* Setting available resolutions and xy ranges for sane frontend. */
+ {
+ hw->dpi_range.min = hw->res.list[1];
+ hw->dpi_range.max = hw->res.list[hw->res.size];
+ if (!hw->using_fs) hw->base_res = hw->res.list[hw->res.size];
+ hw->dpi_range.quant = 0;
+
+ if (hw->fbf)
+ {
+ update_ranges (hw, hw->fbf);
+
+ log_info ("FBF: TL (%.2f, %.2f) -- BR (%.2f, %.2f) [in mm]",
+ SANE_UNFIX (hw->fbf->x_range.min),
+ SANE_UNFIX (hw->fbf->y_range.min),
+ SANE_UNFIX (hw->fbf->x_range.max),
+ SANE_UNFIX (hw->fbf->y_range.max));
+ }
+ }
+
+ copy_resolution_info (&hw->res_x, &hw->res, SANE_FALSE);
+ copy_resolution_info (&hw->res_y, &hw->res, SANE_FALSE);
+
+ return SANE_STATUS_GOOD;
+} /* request identity */
+
+
+static SANE_Status
+get_hardware_property (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ log_call ();
+
+ if (hw->cmd->request_identity2 == 0)
+ return SANE_STATUS_UNSUPPORTED;
+
+ status = cmd_request_hardware_property (hw);
+ if (SANE_STATUS_GOOD != status)
+ {
+ return status;
+ }
+
+ log_info ("optical resolution: %ddpi", hw->optical_res);
+
+ if (hw->line_dist_x != hw->line_dist_y)
+ {
+ return SANE_STATUS_INVAL;
+ }
+ hw->max_line_distance = hw->line_dist_y;
+ if (hw->fbf)
+ {
+ hw->fbf->y_range.max = SANE_FIX ((hw->fbf->max_y -
+ 2 * (hw->max_line_distance))
+ * MM_PER_INCH / hw->base_res);
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+static SANE_Status
+get_identity2_information (device *hw, Epson_Scanner *s)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ require (s && (hw == s->hw));
+
+ status = get_hardware_property (hw);
+ if (SANE_STATUS_GOOD != status)
+ return status;
+
+ /* reset constraints because we are pointing to different lists now
+ FIXME: Only needs to be done the first time we (successfully)
+ send the ESC i command. This should be done when constructing
+ the device and is therefore done by the time we construct a
+ scanner. While the content and size of the lists may vary
+ depending on the selected option, the list nature is constant.
+ */
+ s->opt[OPT_X_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+ s->opt[OPT_X_RESOLUTION].constraint.word_list = hw->res_x.list;
+ s->opt[OPT_Y_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
+ s->opt[OPT_Y_RESOLUTION].constraint.word_list = hw->res_y.list;
+
+ return SANE_STATUS_GOOD;
+}
+
+
+void
+sane_cancel (SANE_Handle handle)
+{
+ Epson_Scanner *s = (Epson_Scanner *) handle;
+
+ log_call ();
+
+ s->img.cancel_requested = true;
+
+ if (s->src->transfer_started && !s->src->transfer_stopped)
+ {
+ SANE_Byte dumpster[1024];
+ int len;
+
+ if (s->src == &s->raw)
+ {
+ s->raw.cancel_requested = true;
+ do
+ {
+ fetch_image_data (s, dumpster, num_of (dumpster), &len);
+ }
+ while (!s->raw.transfer_stopped);
+ }
+
+ if (using (s->hw, adf) && 0 != s->hw->adf->sheet_count
+ && ((EXT_STATUS_ADFT & s->hw->ext_status) /* page type */
+ || adf_has_auto_form_feed (s->hw)))
+ dev_eject_paper (s->hw);
+ }
+ if (!s->raw.cancel_requested && s->raw.all_data_fetched
+ && s->hw->using_fs && s->val[OPT_ADF_AUTO_SCAN].w
+ && SANE_STATUS_NO_DOCS != check_ext_status (s->hw))
+ {
+ s->raw.cancel_requested = dev_force_cancel (s->hw);
+ }
+ dev_unlock (s->hw);
+
+ if (ENABLE_TIMING)
+ {
+ time_stamp (time_scan, stop);
+ time_stats (time_pass_count);
+ time_pass_count = 0;
+ }
+
+ s->frame_count = 0;
+
+ /* Restore original SANE options.
+ * \todo string and word array have to copy individually.
+ */
+ if (s->src->transfer_started)
+ {
+ memcpy (s->val, s->val_bak, (sizeof (s->val[0]) * NUM_OPTIONS));
+ }
+
+ /* release resource hogs between scan sequences */
+ delete (s->img.buf);
+ s->img.cap = 0;
+}
+
+/* Request the push button status returns SANE_TRUE if the button was
+ * pressed and SANE_FALSE if the button was not pressed. It also
+ * returns SANE_TRUE in case of an error. This is necessary so that a
+ * process that waits for the button does not block indefinitely.
+ */
+static SANE_Status
+get_push_button_status (device *hw, SANE_Bool *button_pushed)
+{
+ SANE_Status status;
+ int len;
+ u_char param[3];
+ u_char result[4];
+ u_char *buf;
+
+ log_call ();
+
+ if (hw->cmd->request_push_button_status == 0)
+ {
+ log_info ("push button status unsupported");
+ return SANE_STATUS_UNSUPPORTED;
+ }
+
+ param[0] = ESC;
+ param[1] = hw->cmd->request_push_button_status;
+ param[2] = '\0';
+
+ channel_send (hw->channel, param, 2, &status);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ err_minor ("error sending command");
+ return status;
+ }
+
+ len = 4; /* get info block */
+ channel_recv (hw->channel, result, len, &status);
+ if (SANE_STATUS_GOOD != status)
+ return status;
+
+ /* get button status */
+ hw->status = result[1];
+ len = result[3] << 8 | result[2];
+ buf = t_alloca (len, u_char);
+ channel_recv (hw->channel, buf, len, &status);
+
+ log_info ("Push button status = %d", buf[0] & 0x01);
+ *button_pushed = ((buf[0] & 0x01) != 0);
+
+ return (SANE_STATUS_GOOD);
+}
+
+
+static void
+filter_resolution_list (Epson_Scanner * s)
+{
+ log_call ();
+
+ if (SANE_TRUE == s->val[OPT_LIMIT_RESOLUTION].w)
+ { /* trim list */
+ SANE_Int i;
+
+ s->hw->res.size = 0;
+ for (i = 1; i <= s->hw->resolution.size; i++)
+ {
+ SANE_Word res = s->hw->resolution.list[i];
+
+ if ((res < 100) || (0 == (res % 300)) || (0 == (res % 400)))
+ {
+ s->hw->res.size++;
+ s->hw->res.list[s->hw->res.size] = res;
+ }
+ }
+ }
+ else
+ { /* restore full list */
+ SANE_Int i;
+
+ for (i = 1; i <= s->hw->resolution.size; i++)
+ {
+ s->hw->res.list[i] = s->hw->resolution.list[i];
+ }
+ s->hw->res.size = s->hw->resolution.size;
+ }
+ s->hw->res.list[0] = s->hw->res.size;
+
+ /* We have just created a filtered resolution list on the *full*
+ * list of resolutions. Now apply ADF capping if necessary. No
+ * need to call dev_restore_res as we've already (sorta) done so
+ * above.
+ */
+ if (using (s->hw, adf)
+ && 0 < large_res_kills_adf_scan (s->hw))
+ {
+ dev_limit_res (s->hw, s->opt[OPT_RESOLUTION].constraint_type,
+ large_res_kills_adf_scan (s->hw));
+ }
+ handle_resolution (s, OPT_RESOLUTION, s->val[OPT_RESOLUTION].w);
+}
+
+
+SANE_Status
+sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
+{
+ log_call ("(%s-blocking)", (non_blocking ? "non" : ""));
+
+ /* get rid of compiler warning */
+ handle = handle;
+
+ return SANE_STATUS_UNSUPPORTED;
+}
+
+
+SANE_Status
+sane_get_select_fd (SANE_Handle handle, SANE_Int * fd)
+{
+ log_call ();
+
+ /* get rid of compiler warnings */
+ handle = handle;
+ fd = fd;
+
+ return SANE_STATUS_UNSUPPORTED;
+}
+
+
+SANE_Status
+control_option_unit (device *hw, SANE_Bool use_duplex)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+ u_char value = 0;
+
+ log_call ();
+
+ if (!hw) return SANE_STATUS_INVAL;
+ if (!(hw->adf || hw->tpu)) return SANE_STATUS_GOOD;
+
+ value = using (hw, adf) || using (hw, tpu);
+ if (value && use_duplex)
+ {
+ value = 2;
+ hw->adf->using_duplex = SANE_TRUE;
+ }
+
+ status = set_cmd (hw, hw->cmd->control_an_extension, value);
+
+ if (using (hw, adf)
+ && (0 == strcmp_c ("ES-10000G", hw->fw_name)
+ || 0 == strcmp_c ("Expression10000", hw->fw_name)))
+ {
+ u_char *buf;
+ u_char params[2];
+ EpsonHdr head;
+ int failures_allowed = 5;
+
+ params[0] = ESC;
+ params[1] = hw->cmd->request_extended_status;
+
+ head = (EpsonHdr) command (hw, params, 2, &status);
+ buf = &head->buf[0];
+
+ while (!(buf[1] & ADF_STATUS_EN))
+ {
+ sleep (1);
+ status = set_cmd (hw, hw->cmd->control_an_extension, value);
+
+ head = (EpsonHdr) command (hw, params, 2, &status);
+ if (SANE_STATUS_GOOD != status)
+ {
+ --failures_allowed;
+ if (!failures_allowed)
+ return status;
+ }
+ buf = &head->buf[0];
+ }
+ }
+ return status;
+}
+
+/* Fetches the main/sub resolution lists for the device.
+ */
+static SANE_Status
+get_resolution_constraints (device *hw, Epson_Scanner *s)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+ SANE_Bool adf_duplex;
+
+ log_call ();
+
+ if (!hw->cmd->request_identity2)
+ return SANE_STATUS_GOOD;
+
+ /* if it is previewing now, disable duplex */
+ adf_duplex = ((1 == s->val[OPT_ADF_MODE].w)
+ && !s->val[OPT_PREVIEW].b);
+ status = control_option_unit (hw, adf_duplex);
+ status = get_identity2_information (hw, s);
+
+ handle_resolution (s, OPT_X_RESOLUTION, DEFAULT_X_RESOLUTION);
+ handle_resolution (s, OPT_Y_RESOLUTION, DEFAULT_Y_RESOLUTION);
+
+ return status;
+}
+
+
+static scan_area_t get_model_info_max_scan_area (device *hw, int adf_mode)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+ scan_area_t scan_area;
+
+ scan_area.width = SANE_FIX(-1);
+ scan_area.height = SANE_FIX(-1);
+
+ if(hw->adf){
+ const void *info = model_info_cache_get_info (hw->fw_name, &status);
+ if (SANE_STATUS_GOOD == status && info){
+ const char *mode = (1 == adf_mode ? "duplex": "simplex");
+
+ scan_area = model_info_max_scan_area(info, "adf", mode);
+ }
+ }
+ return scan_area;
+}
+
+
+static SANE_Status handle_scan_area(Epson_Scanner *s, int adf_mode)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+ scan_area_t scan_area;
+
+ if(s->hw->adf){
+ scan_area = get_model_info_max_scan_area (s->hw, adf_mode);
+
+ if(SANE_UNFIX (scan_area.width) >= 0
+ && SANE_UNFIX (scan_area.height) >= 0){
+ s->hw->adf->x_range.max = scan_area.width;
+ s->hw->adf->y_range.max = scan_area.height;
+ }else{
+ err_minor ("failure getting model info (%s)", sane_strstatus (status));
+ }
+ }
+
+ {
+ SANE_Option_Descriptor *sopt = &(s->opt[OPT_SCAN_AREA]);
+ Option_Value *sval = &(s->val[OPT_SCAN_AREA]);
+
+ SANE_String_Const area = NULL;
+ SANE_String_Const *p
+ = (SANE_String_Const *) sopt->constraint.string_list;
+
+ if (!p)
+ {
+ s->opt[OPT_SCAN_AREA].cap |= SANE_CAP_INACTIVE;
+
+ /* media_list + media_maximum, media_automatic, and NULL terminator */
+ p = t_calloc (num_of (media_list) + 3, SANE_String_Const);
+
+ if (!p) /* do without the option */
+ {
+ s->opt[OPT_QUICK_FORMAT].cap = s->opt[OPT_SCAN_AREA].cap;
+ return status;
+ }
+
+ sopt->constraint.string_list = p;
+ }
+ area = p[sval->w];
+
+ memset (p, 0, (num_of (media_list) + 3) * sizeof (SANE_String_Const));
+ sopt->size = 0;
+
+ *p = media_maximum;
+ sopt->size = max (strlen (*p), sopt->size);
+ ++p;
+
+ if (using (s->hw, tpu))
+ {
+ s->opt[OPT_SCAN_AREA].cap |= SANE_CAP_INACTIVE;
+ }
+ else
+ {
+ size_t i;
+
+ if (has_size_check_support (s->hw->src))
+ {
+ *p = media_automatic;
+ sopt->size = max (strlen (*p) + 1, sopt->size);
+ ++p;
+ s->opt[OPT_SCAN_AREA].cap |= SANE_CAP_AUTOMATIC;
+ }
+
+ for (i = 0; i < num_of (media_list); ++i)
+ {
+ SANE_Fixed w = SANE_FIX (media_list[i].width);
+ SANE_Fixed h = SANE_FIX (media_list[i].height);
+
+ if ( w <= s->hw->src->x_range.max
+ && h <= s->hw->src->y_range.max)
+ {
+ *p = media_list[i].name;
+ sopt->size = max (strlen (*p) + 1, sopt->size);
+ ++p;
+ if (strstr (media_list[i].name, "Portrait"))
+ {
+ ++i;
+ }
+ }
+ else if (strstr (media_list[i].name, "Landscape"))
+ { /* no need to list "Portrait" */
+ ++i;
+ }
+ }
+
+ s->opt[OPT_SCAN_AREA].cap &= ~SANE_CAP_INACTIVE;
+ }
+
+ { // recompute index of selected area in the new list
+ size_t i = 0;
+ p = (SANE_String_Const *) sopt->constraint.string_list;
+ while (*p && 0 != strcmp_c (*p, area))
+ ++i, ++p;
+ sval->w = (*p ? i : 0);
+ }
+ }
+
+ s->val[OPT_BR_X].w = s->hw->src->x_range.max;
+ s->val[OPT_BR_Y].w = s->hw->src->y_range.max;
+ s->val[OPT_TL_X].w = 0;
+ s->val[OPT_TL_Y].w = 0;
+
+ s->opt[OPT_TL_X].constraint.range = &(s->hw->src->x_range);
+ s->opt[OPT_TL_Y].constraint.range = &(s->hw->src->y_range);
+ s->opt[OPT_BR_X].constraint.range = &(s->hw->src->x_range);
+ s->opt[OPT_BR_Y].constraint.range = &(s->hw->src->y_range);
+
+ /* copy OPT_SCAN_AREA to OPT_QUICK_FORMAT */
+ s->opt[OPT_QUICK_FORMAT].size = s->opt[OPT_SCAN_AREA].size;
+ s->opt[OPT_QUICK_FORMAT].cap = s->opt[OPT_SCAN_AREA].cap;
+ s->opt[OPT_QUICK_FORMAT].constraint.string_list
+ = s->opt[OPT_SCAN_AREA].constraint.string_list;
+ s->val[OPT_QUICK_FORMAT].w = s->val[OPT_SCAN_AREA].w;
+
+ return status;
+}
diff --git a/backend/epkowa.conf b/backend/epkowa.conf
new file mode 100644
index 0000000..fd387b1
--- /dev/null
+++ b/backend/epkowa.conf
@@ -0,0 +1,65 @@
+# epkowa.conf -- sample configuration for the EPKOWA SANE backend
+# Copyright (C) 2004, 2008, 2009 Olaf Meeuwissen
+#
+# See sane-epkowa(5), sane-usb(5) and sane-scsi(5) for details.
+
+# Detect all devices supported by the backend.
+# If you don't have a SCSI device, you can comment out the "scsi"
+# keyword. Similarly for the other keywords.
+#
+usb
+scsi
+
+
+# For any USB scanner not known to the backend (yet), you may, at your
+# own peril(!!), force the backend to recognise and use it via libusb.
+# You can do so by the following configuration command:
+#
+# usb <USB vendor ID> <USB product ID>
+#
+# SEIKO EPSON's USB vendor ID is '0x04b8' (without quotes). In order
+# to find the USB product ID, use lsusb(1).
+# A sample configuration for the Epson Perfection 1650 (Epson GT-8200),
+# which has a product ID of 0x0110, would look as follows:
+#
+#usb 0x04b8 0x0110
+
+
+# For SCSI devices not detected, you can add an entry like:
+#
+# scsi EPSON GT-20000
+#
+# where the GT-20000 bit corresponds to the SCSI model information as
+# shown in the output of dmesg(1) or in the /var/log/kern.log file.
+
+
+# Network attached devices may be made to work by first installing the
+# (non-free) iscan-network-nt package and then adding configuration lines
+# as per information below.
+#
+# For each network attached device, you must add an entry as follows:
+#
+# net <IP-address|hostname> [port-number]
+#
+# Ask your network administrator for the device's IP address or check
+# for yourself on the panel (if it has one). The port-number is very
+# optional and defaults to 1865.
+# Note that network attached devices are not queried unless configured
+# in this file.
+#
+# Examples:
+#
+#net 192.16.136.2 1865
+#net 10.0.0.1
+#net scanner.mydomain.com
+
+
+# Some backend behaviour can be customized by using the option keyword
+# followed by an option name, as shown below.
+#
+# option <option-name>
+#
+# Currently available options:
+#
+# Makes the automatic document feeder the default document source
+#option prefer-adf
diff --git a/backend/epkowa.h b/backend/epkowa.h
new file mode 100644
index 0000000..72ab909
--- /dev/null
+++ b/backend/epkowa.h
@@ -0,0 +1,291 @@
+/* epkowa.h - SANE backend for EPSON flatbed scanners
+ (Image Scan! version)
+
+ Based on the SANE Epson backend (originally from sane-1.0.3)
+ - updated to sane-backends-1.0.6
+ - renamed from epson to epkowa to avoid confusion
+ - updated to sane-backends-1.0.10
+ - updated to sane-backends-1.0.15
+
+ Based on Kazuhiro Sasayama previous
+ Work on epson.[ch] file from the SANE package.
+
+ Original code taken from sane-0.71
+ Copyright (C) 1997 Hypercore Software Design, Ltd.
+
+ modifications
+ Copyright (C) 1998-1999 Christian Bucher <bucher@vernetzt.at>
+ Copyright (C) 1998-1999 Kling & Hautzinger GmbH
+ Copyright (C) 1999 Norihiko Sawa <sawa@yb3.so-net.ne.jp>
+ Copyright (C) 2000 Karl Heinz Kremer <khk@khk.net>
+ Copyright (C) 2001-2009 SEIKO EPSON CORPORATION
+
+ This file is part of the EPKOWA SANE backend.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice. */
+
+#ifndef epkowa_h
+#define epkowa_h
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "device.h"
+
+typedef struct
+{
+ SANE_Byte *lut;
+ int depth;
+
+} LUT;
+
+
+/* convenience union to access option values given to the backend
+ */
+typedef union
+{
+ SANE_Bool b;
+ SANE_Word w;
+ SANE_Word *wa; /* word array */
+ SANE_String s;
+}
+Option_Value;
+
+/* string constants for GUI elements that are not defined SANE-wide */
+
+#define SANE_NAME_GAMMA_CORRECTION "gamma-correction"
+#define SANE_TITLE_GAMMA_CORRECTION SANE_I18N("Gamma Correction")
+#define SANE_DESC_GAMMA_CORRECTION SANE_I18N("Selects the gamma correction value from a list of pre-defined devices or the user defined table, which can be downloaded to the scanner")
+
+#define SANE_EPSON_FOCUS_NAME "focus-position"
+#define SANE_EPSON_FOCUS_TITLE SANE_I18N("Focus Position")
+#define SANE_EPSON_FOCUS_DESC SANE_I18N("Sets the focus position to either the glass or 2.5mm above the glass")
+#define SANE_EPSON_WAIT_FOR_BUTTON_NAME "wait-for-button"
+#define SANE_EPSON_WAIT_FOR_BUTTON_TITLE SANE_I18N("Wait for Button")
+#define SANE_EPSON_WAIT_FOR_BUTTON_DESC SANE_I18N("After sending the scan command, wait until the button on the scanner is pressed to actually start the scan process.");
+#define SANE_EPSON_MONITOR_BUTTON_NAME "monitor-button"
+#define SANE_EPSON_MONITOR_BUTTON_TITLE SANE_I18N("Monitor Button")
+#define SANE_EPSON_MONITOR_BUTTON_DESC SANE_I18N("Indicates whether a button on the scanner has been pressed.");
+#define SANE_EPSON_DETECT_DOC_SIZE_NAME "detect-doc-size"
+#define SANE_EPSON_DETECT_DOC_SIZE_TITLE SANE_I18N("Auto-detect document size")
+#define SANE_EPSON_DETECT_DOC_SIZE_DESC SANE_I18N("Activates document size auto-detection. The scan area will be set to match the detected document size.")
+#define SANE_EPSON_SCAN_AREA_IS_VALID_NAME "scan-area-is-valid"
+#define SANE_EPSON_SCAN_AREA_IS_VALID_TITLE SANE_I18N("Scan Area Is Valid")
+#define SANE_EPSON_SCAN_AREA_IS_VALID_DESC SANE_I18N("Indicates whether the current scan area settings are valid.")
+
+#define SANE_EPSON_ADF_AUTO_SCAN_NAME "adf-auto-scan"
+#define SANE_EPSON_ADF_AUTO_SCAN_TITLE SANE_I18N("ADF Auto Scan")
+#define SANE_EPSON_ADF_AUTO_SCAN_DESC SANE_I18N("Skips per sheet device setup for faster throughput.")
+
+#define SANE_EPSON_ADF_DFD_SENSITIVITY_NAME "double-feed-detection-sensitivity"
+#define SANE_EPSON_ADF_DFD_SENSITIVITY_TITLE SANE_I18N("Double Feed Detection Sensitivity")
+#define SANE_EPSON_ADF_DFD_SENSITIVITY_DESC SANE_I18N("Sets the sensitivity with which multi-sheet page feeds are detected and reported as errors.")
+
+#define SANE_EPSON_ADF_DUPLEX_DIRECTION_MATCHES_NAME "adf-duplex-direction-matches"
+#define SANE_EPSON_ADF_DUPLEX_DIRECTION_MATCHES_TITLE SANE_I18N("ADF Duplex Direction Matches")
+#define SANE_EPSON_ADF_DUPLEX_DIRECTION_MATCHES_DESC SANE_I18N("Indicates whether the device's ADF duplex mode, if available, scans in the same direction for the front and back.")
+
+#define SANE_EPSON_POLLING_TIME_NAME "polling-time"
+#define SANE_EPSON_POLLING_TIME_TITLE SANE_I18N("Polling Time")
+#define SANE_EPSON_POLLING_TIME_DESC SANE_I18N("Time between queries when waiting for device state changes.")
+
+#define SANE_EPSON_NEEDS_POLLING_NAME "needs-polling"
+#define SANE_EPSON_NEEDS_POLLING_TITLE SANE_I18N("Needs Polling")
+#define SANE_EPSON_NEEDS_POLLING_DESC SANE_I18N("Indicates whether the scanner needs to poll.")
+
+#define SANE_EPSON_CALIBRATE_NAME "calibrate"
+#define SANE_EPSON_CALIBRATE_TITLE SANE_I18N("Calibrate")
+#define SANE_EPSON_CALIBRATE_DESC SANE_I18N("Performs color matching to make sure that the document's color tones are scanned correctly.")
+
+#define SANE_EPSON_CLEAN_NAME "clean"
+#define SANE_EPSON_CLEAN_TITLE SANE_I18N("Clean")
+#define SANE_EPSON_CLEAN_DESC SANE_I18N("Cleans the scanners reading section.")
+
+#define LINES_SHUFFLE_MAX (17) /* 2 x 8 lines plus 1 */
+
+#define SANE_EPSON_MAX_RETRIES (120) /* how often do we retry during warmup ? */
+
+#define MAX_READ_ATTEMPTS 10 /* maximum number of attempts at
+ reading scan data */
+#define DEFAULT_POLLING_TIME (1000 * 1000) /* usec */
+
+enum {
+ OPT_NUM_OPTS = 0,
+ /* */
+ OPT_MODE_GROUP,
+ OPT_MODE,
+ OPT_BIT_DEPTH,
+ OPT_HALFTONE,
+ OPT_DROPOUT,
+ OPT_BRIGHTNESS_METHOD,
+ OPT_BRIGHTNESS,
+ OPT_CONTRAST,
+ OPT_SHARPNESS,
+ OPT_GAMMA_CORRECTION,
+ OPT_COLOR_CORRECTION,
+ OPT_RESOLUTION,
+ OPT_X_RESOLUTION,
+ OPT_Y_RESOLUTION,
+ OPT_THRESHOLD,
+ /* */
+ OPT_ADVANCED_GROUP,
+ OPT_MIRROR,
+ OPT_SPEED,
+ OPT_AAS,
+ OPT_LIMIT_RESOLUTION,
+ OPT_ZOOM,
+ OPT_GAMMA_VECTOR_R,
+ OPT_GAMMA_VECTOR_G,
+ OPT_GAMMA_VECTOR_B,
+ OPT_WAIT_FOR_BUTTON,
+ OPT_MONITOR_BUTTON,
+ OPT_POLLING_TIME,
+ OPT_NEEDS_POLLING,
+ /* */
+ OPT_CCT_GROUP,
+ OPT_CCT_1,
+ OPT_CCT_2,
+ OPT_CCT_3,
+ OPT_CCT_4,
+ OPT_CCT_5,
+ OPT_CCT_6,
+ OPT_CCT_7,
+ OPT_CCT_8,
+ OPT_CCT_9,
+ /* */
+ OPT_PREVIEW_GROUP,
+ OPT_PREVIEW,
+ OPT_PREVIEW_SPEED,
+ /* */
+ OPT_GEOMETRY_GROUP,
+ OPT_SCAN_AREA,
+ OPT_TL_X,
+ OPT_TL_Y,
+ OPT_BR_X,
+ OPT_BR_Y,
+ OPT_QUICK_FORMAT,
+ /* */
+ OPT_EQU_GROUP,
+ OPT_SOURCE,
+ OPT_AUTO_EJECT,
+ OPT_FILM_TYPE,
+ OPT_FOCUS,
+ OPT_BAY,
+ OPT_EJECT,
+ OPT_ADF_MODE,
+ OPT_DETECT_DOC_SIZE,
+ OPT_SCAN_AREA_IS_VALID,
+ OPT_ADF_AUTO_SCAN,
+ OPT_ADF_DFD_SENSITIVITY,
+ OPT_EXT_SANE_STATUS,
+ OPT_ADF_DUPLEX_DIRECTION_MATCHES,
+ OPT_DESKEW,
+ OPT_AUTOCROP,
+ OPT_CALIBRATE,
+ OPT_CLEAN,
+ /* */
+ NUM_OPTIONS
+};
+
+/*! Container for image data.
+ */
+typedef struct
+{
+ size_t cap; /*!< buffer capacity */
+ SANE_Byte *buf; /*!< start of image data */
+ SANE_Byte *end; /*!< end of image data */
+ SANE_Byte *ptr; /*!< current position */
+ SANE_Parameters ctx; /*!< buffer context */
+
+ bool cancel_requested;
+ bool all_data_fetched;
+ bool transfer_started;
+ bool transfer_stopped;
+
+} buffer;
+
+/*! Software representation of the \e logical device.
+ */
+struct Epson_Scanner
+{
+ struct Epson_Scanner *next;
+
+ const void *dip;
+ device *hw;
+ SANE_Option_Descriptor opt[NUM_OPTIONS];
+ Option_Value val[NUM_OPTIONS];
+ Option_Value val_bak[NUM_OPTIONS];
+
+ /* image data acquisition buffers and parameters */
+ buffer *src; /*!< buffer to provide data to frontend */
+ buffer raw; /*!< device image data blocks */
+ buffer img; /*!< complete in-memory image */
+
+ SANE_Byte *line_buffer[LINES_SHUFFLE_MAX];
+ size_t cap_line_buffer;
+
+ SANE_Int color_shuffle_line; /* current line number for color shuffling */
+ SANE_Int line_distance; /* current line distance */
+ SANE_Int current_output_line; /* line counter when color shuffling */
+
+ SANE_Bool invert_image;
+ SANE_Word gamma_table[3][256];
+ double cct[9];
+ LUT *lut;
+ double brightness;
+ double contrast;
+
+ /*! Number of frames acquired so far.
+ * This includes partial frames if scan_finish() is called before a
+ * frame is completed.
+ */
+ unsigned int frame_count;
+
+ /*! Number of raw scan lines data gotten from scanner
+ * This corresponds to the \c ESC_d parameter.
+ */
+ unsigned int line_count;
+};
+
+typedef struct Epson_Scanner Epson_Scanner;
+
+SANE_Status estimate_parameters (Epson_Scanner *, SANE_Parameters *);
+SANE_Status fetch_image_data (Epson_Scanner *, SANE_Byte *, SANE_Int,
+ SANE_Int *);
+
+#endif /* not epkowa_h */
diff --git a/backend/epkowa_ip.c b/backend/epkowa_ip.c
new file mode 100644
index 0000000..763a34a
--- /dev/null
+++ b/backend/epkowa_ip.c
@@ -0,0 +1,596 @@
+/* epkowa_ip.c -- for scanners that don't speak EPSON ESC/I natively
+ Copyright (C) 2005--2008 Olaf Meeuwissen
+ Copyright (C) 2009 SEIKO EPSON CORPORATION
+
+ This file is part of the EPKOWA SANE backend.
+ It defines a wrapper around the backend's "Interpreter" interface.
+
+ The EPKOWA SANE backend is free software.
+ You can redistribute it and/or modify it under the terms of the GNU
+ General Public License as published by the Free Software Foundation;
+ either version 2 of the License or at your option any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+ FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+ See the GNU General Public License for more details.
+
+ You should have received a verbatim copy of the GNU General Public
+ License along with this program; if not, write to:
+
+ Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330
+ Boston, MA 02111-1307 USA
+
+ Linking the EPKOWA SANE backend statically or dynamically with
+ other modules is making a combined work based on the EPKOWA SANE
+ backend. Thus, the terms and conditions of the GNU General Public
+ License cover the whole combination.
+
+ As a special exception, the copyright holders of the EPKOWA SANE
+ backend give you permission to link the EPKOWA SANE backend with
+ independent modules that communicate with the EPKOWA SANE backend
+ solely through the "Interpreter" interface, regardless of the
+ license terms of these independent modules, and to copy and
+ distribute the resulting combined work under terms of your choice,
+ provided that every copy of the combined work is accompanied by a
+ complete copy of the source code of the EPKOWA SANE backend (the
+ version of the EPKOWA SANE backend used to produce the combined
+ work), being distributed under the terms of the GNU General Public
+ License plus this exception. An independent module is a module
+ which is not derived from or based on the EPKOWA SANE backend.
+
+ Note that people who make modified versions of the EPKOWA SANE
+ backend are not obligated to grant this special exception for their
+ modified versions; it is their choice whether to do so. The GNU
+ General Public License gives permission to release a modified
+ version without this exception; this exception also makes it
+ possible to release a modified version which carries forward this
+ exception.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "epkowa_ip.h"
+#include "cfg-obj.h"
+
+#include "sane/sanei_usb.h"
+
+#define TICK_INTERVAL 60 /* seconds */
+#define TICK_TIME_OUT 15 /* intervals */
+
+// FIXME: this ought to be part of the interpreter_type struct so that
+// every instance can have its own.
+device_type *g_epson = NULL;
+
+
+/* default member function implementors */
+
+static SANE_Status _dtor (device_type *);
+static int _open (device_type *);
+static int _close (device_type *);
+static ssize_t _recv (device_type *, void *, size_t, SANE_Status *);
+static ssize_t _send (device_type *, const void *, size_t, SANE_Status *);
+
+static SANE_Status _free (device_type *);
+static SANE_Status _ftor0 (device_type *, SANE_Parameters *,
+ SANE_Byte *, SANE_Byte *);
+static SANE_Status _ftor1 (device_type *, SANE_Parameters *,
+ int, int, int, int);
+
+/* private functions */
+
+static void *_load (const char *, device_type *);
+
+
+/* callback functions */
+
+static ssize_t usb_read (void *buffer, size_t size);
+static ssize_t usb_write (void *buffer, size_t size);
+static ssize_t usb_ctrl (size_t request_type, size_t request,
+ size_t value, size_t index,
+ size_t size, void *buffer);
+
+
+/*! Creates an interpreter object for a \a device if both necessary
+ and available. The object is accessible through the \a device's
+ \c interpreter field once successfully created.
+ */
+SANE_Status
+create_interpreter (device_type *device, unsigned int usb_product_id)
+{
+ void *cfg = cfg_init (NULL, NULL);
+ list *seen = cfg_seen (cfg, CFG_KEY_INTERPRETER);
+ const cfg_interpreter_info *next = NULL;
+
+ if (!device) /* sanity check */
+ {
+ return SANE_STATUS_INVAL;
+ }
+
+ if (device->interpreter) /* we've been here before */
+ {
+ if (device != device->interpreter->_device)
+ { /* internal inconsistency! */
+ return SANE_STATUS_INVAL;
+ }
+ return SANE_STATUS_GOOD;
+ }
+
+ if (seen)
+ {
+ list_entry *cur = seen->cur;
+ list_reset (seen);
+ while ((next = list_next (seen))
+ && usb_product_id != next->product)
+ {
+ /* condition does all the processing */
+ }
+ seen->cur = cur;
+ }
+ if (!next) return SANE_STATUS_GOOD;
+
+ /* If we are still here, we need to set up an interpreter and load
+ the corresponding module. */
+
+ device->interpreter = t_malloc (1, struct interpreter_type);
+
+ if (!device->interpreter)
+ {
+ return SANE_STATUS_NO_MEM;
+ }
+
+ device->interpreter->_device = device;
+ device->interpreter->_module = _load (next->library, device);
+
+ if (!device->interpreter->_module)
+ {
+ delete (device->interpreter);
+ return SANE_STATUS_INVAL;
+ }
+
+ device->interpreter->_tick_count = -1;
+ device->interpreter->_table = NULL;
+ device->interpreter->_buffer = NULL;
+
+ device->interpreter->dtor = _dtor;
+ device->interpreter->open = _open;
+ device->interpreter->close = _close;
+ device->interpreter->recv = _recv;
+ device->interpreter->send = _send;
+
+ device->interpreter->free = _free;
+ device->interpreter->ftor0 = _ftor0;
+ device->interpreter->ftor1 = _ftor1;
+
+ return SANE_STATUS_GOOD;
+}
+
+
+/*! Destroys a \a device's interpreter object.
+ */
+SANE_Status
+_dtor (device_type *device)
+{
+ if (!device || !device->interpreter)
+ {
+ return SANE_STATUS_INVAL;
+ }
+
+ device->interpreter->close (device);
+ device->interpreter->free (device);
+
+#ifdef USE_ALARM
+ alarm (0);
+#endif
+
+ lt_dlclose (device->interpreter->_module);
+ device->interpreter->_module = 0;
+
+ delete (device->interpreter);
+
+ return SANE_STATUS_GOOD;
+}
+
+/*! Frees several temporary buffers.
+ */
+SANE_Status
+_free (device_type *device)
+{
+ if (!device || !device->interpreter)
+ {
+ return SANE_STATUS_INVAL;
+ }
+
+ delete (device->interpreter->_table);
+ delete (device->interpreter->_buffer);
+
+ return SANE_STATUS_GOOD;
+}
+
+/*! Performs whatever actions are needed when opening an interpreter
+ "controlled" device. One of these actions is disarming an alarm
+ used to control whether the device can go into power saving mode
+ or not. */
+int
+_open (device_type *device)
+{
+ struct interpreter_type *di;
+
+ if (!device || !device->interpreter)
+ {
+ return -1;
+ }
+ di = device->interpreter;
+
+#ifdef USE_ALARM
+ alarm (0);
+ log_info ("alarm (%d)", 0);
+#endif
+ di->_tick_count = -1;
+
+ g_epson = device;
+
+ if (device->fd < 0
+ || !(di->_init
+ ? di->_init (device->fd, usb_read, usb_write)
+ : di->_init_with_ctrl (device->fd, usb_read,
+ usb_write, usb_ctrl)))
+ {
+ err_fatal ("failed to initialize interpreter");
+ g_epson = NULL;
+ return -1;
+ }
+
+ return device->fd;
+}
+
+int
+_close (device_type *device)
+{
+ if (!device || !device->interpreter)
+ {
+ return -1;
+ }
+
+ device->interpreter->_fini ();
+
+ device->interpreter->_tick_count = 0;
+#ifdef USE_ALARM
+ alarm (TICK_INTERVAL);
+ log_info ("alarm (%d)", TICK_INTERVAL);
+#endif
+
+ g_epson = NULL;
+
+ return device->fd;
+}
+
+ssize_t
+_send (device_type *device, const void *buffer, size_t size,
+ SANE_Status *status)
+{
+ if (!status)
+ {
+ return -1;
+ }
+
+ if (!device || !device->interpreter)
+ {
+ *status = SANE_STATUS_INVAL;
+ return -1;
+ }
+
+ /* ASSUMPTION: Interpreter does NOT change buffer's content.
+ */
+ if (device->interpreter->_write ((void *)buffer, size))
+ {
+ *status = SANE_STATUS_GOOD;
+ }
+ else
+ {
+ *status = SANE_STATUS_INVAL;
+ }
+
+ return size;
+}
+
+ssize_t
+_recv (device_type *device, void *buffer, size_t size,
+ SANE_Status *status)
+{
+ if (!status)
+ {
+ return -1;
+ }
+
+ if (!device || !device->interpreter)
+ {
+ *status = SANE_STATUS_INVAL;
+ return -1;
+ }
+
+ if (device->interpreter->_read (buffer, size))
+ {
+ *status = SANE_STATUS_GOOD;
+ }
+ else
+ {
+ *status = SANE_STATUS_INVAL;
+ }
+
+ return size;
+}
+
+/*! Callback for use by the interpreter. */
+ssize_t
+usb_read (void *buffer, size_t size)
+{
+ size_t n = size;
+
+ if (!g_epson || g_epson->fd < 0)
+ {
+ return 0;
+ }
+
+ if (SANE_STATUS_GOOD == sanei_usb_read_bulk (g_epson->fd, buffer, &n))
+ {
+ if (size != n)
+ err_minor ("Did not read number of bytes requested");
+ return n;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+/*! Callback for use by the interpreter. */
+ssize_t
+usb_write (void *buffer, size_t size)
+{
+ size_t n = size;
+
+ if (!g_epson || g_epson->fd < 0)
+ {
+ return 0;
+ }
+
+ if (SANE_STATUS_GOOD == sanei_usb_write_bulk (g_epson->fd, buffer, &n))
+ {
+ if (size != n)
+ err_minor ("Did not read number of bytes requested");
+ return n;
+ }
+ else
+ return 0;
+}
+
+/*! Callback for use by the interpreter. */
+ssize_t
+usb_ctrl (size_t request_type, size_t request, size_t value,
+ size_t index, size_t size, void *buffer)
+{
+ size_t n = size;
+
+ if (!g_epson || g_epson->fd < 0)
+ {
+ return 0;
+ }
+
+ if (SANE_STATUS_GOOD == sanei_usb_control_msg (g_epson->fd, request_type,
+ request, value, index,
+ &n, buffer))
+ {
+ if (size != n)
+ err_minor ("Did not read number of bytes requested");
+ return n;
+ }
+ else
+ return 0;
+}
+
+#ifdef USE_ALARM
+void
+timer_event (int sig)
+{
+ sig = sig;
+
+ log_call ();
+
+ if (!g_epson || !g_epson->interpreter)
+ return;
+
+ if ((g_epson && 0 < g_epson->fd) || g_epson->interpreter->_tick_count == -1)
+ {
+ return;
+ }
+
+ g_epson->interpreter->_tick_count++;
+
+ if (TICK_TIME_OUT - 1 <= g_epson->interpreter->_tick_count)
+ {
+ g_epson->interpreter->_power ();
+ g_epson->interpreter->_tick_count = -1;
+ return;
+ }
+
+ alarm (TICK_INTERVAL);
+ log_info ("alarm (%d)", TICK_INTERVAL);
+}
+#endif
+
+/*! Loads the interpreter module. */
+/*!
+ */
+void *
+_load (const char *name, device_type *device)
+{
+ void *handle = NULL;
+ struct interpreter_type *di = device->interpreter;
+
+ /* FIXME: should set the search path to a list of directories based
+ on the regular load path with the PACKAGE attached to each of the
+ individual directories. Should _not_ look in the regular load
+ path's directories. */
+ {
+ const char *path = lt_dlgetsearchpath ();
+ if (!(path && strstr (path, PKGLIBDIR)))
+ {
+ lt_dladdsearchdir (PKGLIBDIR);
+ }
+ handle = lt_dlopenext (name);
+ }
+ if (!handle)
+ {
+ err_fatal ("%s", lt_dlerror());
+ return NULL;
+ }
+
+ di->_init_with_ctrl = lt_dlsym (handle, "int_init_with_ctrl");
+ if (di->_init_with_ctrl)
+ {
+ di->_init = NULL;
+ }
+ else
+ {
+ di->_init = lt_dlsym (handle, "int_init");
+ }
+ di->_fini = lt_dlsym (handle, "int_fini");
+ di->_read = lt_dlsym (handle, "int_read");
+ di->_write = lt_dlsym (handle, "int_write");
+ di->_power = lt_dlsym (handle, "int_power_saving_mode");
+ di->_s_0 = lt_dlsym (handle, "function_s_0");
+ di->_s_1 = lt_dlsym (handle, "function_s_1");
+
+ if (!
+ ( (di->_init || di->_init_with_ctrl)
+ && di->_fini
+ && di->_read
+ && di->_write
+ && di->_s_0
+ && di->_s_1)
+ )
+ {
+ err_fatal ("failed to find all required interpreter API");
+ di->_init_with_ctrl = NULL;
+ di->_init = NULL;
+ di->_fini = NULL;
+ di->_read = NULL;
+ di->_write = NULL;
+ di->_power = NULL;
+ di->_s_0 = NULL;
+ di->_s_1 = NULL;
+ lt_dlclose (handle);
+ return NULL;
+ }
+
+ /* FIXME: if we are still here, we should set up the alarm stuff
+ here because _load is really a sort of ctor. Note that open
+ should disarm the alarm and close should re-arm it. Our dtor
+ should take care of disarming the alarm permanently. */
+ /* FIXME: add error handling */
+ { /* set up alarm for power saving */
+#ifdef USE_ALARM
+ struct sigaction act;
+
+ /* FIXME: set act.sa_sigaction instead because that can hold a
+ void (*) (int, siginfo_t *, void *) so we can pass arguments
+ such as device->interpreter->_device(!) to it, perhaps. */
+ act.sa_handler = timer_event;
+ sigemptyset (&act.sa_mask);
+ act.sa_flags = 0;
+
+ sigaction (SIGALRM, &act, 0);
+
+ alarm (TICK_INTERVAL);
+ log_info ("alarm (%d)", TICK_INTERVAL);
+#endif
+ di->_tick_count = 0;
+ }
+ return handle;
+}
+
+/*
+ This functor is called by sane_read only.
+ */
+SANE_Status
+_ftor0 (device_type *device, SANE_Parameters *params,
+ SANE_Byte *ptr, SANE_Byte *end)
+{
+ if (!device || !device->interpreter || !params)
+ {
+ return SANE_STATUS_INVAL;
+ }
+
+ if (params->depth != 1
+ && device->interpreter->_table && device->interpreter->_buffer)
+ {
+ int i, row;
+
+ row = (end - ptr) / params->bytes_per_line;
+
+ for (i = 0; i < row; ++i)
+ {
+ memcpy (device->interpreter->_buffer, ptr + i * params->bytes_per_line,
+ params->bytes_per_line);
+ device->interpreter->_s_1 (device->interpreter->_buffer,
+ ptr + i * params->bytes_per_line,
+ params->pixels_per_line,
+ params->format == SANE_FRAME_RGB,
+ device->interpreter->_table);
+ }
+ }
+ return SANE_STATUS_GOOD;
+}
+
+/*
+ This functor is called by sane_start only.
+ */
+SANE_Status
+_ftor1 (device_type *device, SANE_Parameters *params,
+ int depth, int left, int x_dpi, int optical_res)
+{
+ if (!device || !device->interpreter || !params)
+ {
+ return SANE_STATUS_INVAL;
+ }
+
+ device->interpreter->free (device);
+
+ if (depth != 1)
+ {
+ device->interpreter->_table =
+ t_malloc (params->pixels_per_line, double);
+
+ if (!device->interpreter->_table)
+ {
+ return SANE_STATUS_NO_MEM;
+ }
+
+ if (device->interpreter->_s_0 (left, params->pixels_per_line,
+ x_dpi, optical_res,
+ device->interpreter->_table))
+ {
+ device->interpreter->_buffer
+ = t_malloc (params->bytes_per_line, SANE_Byte);
+
+ if (!device->interpreter->_buffer)
+ {
+ delete (device->interpreter->_table);
+ return SANE_STATUS_NO_MEM;
+ }
+ }
+ else
+ {
+ delete (device->interpreter->_table);
+ }
+ }
+ return SANE_STATUS_GOOD;
+}
diff --git a/backend/epkowa_ip.h b/backend/epkowa_ip.h
new file mode 100644
index 0000000..cc0fe84
--- /dev/null
+++ b/backend/epkowa_ip.h
@@ -0,0 +1,124 @@
+/* epkowa_ip.h -- for scanners that don't speak EPSON ESC/I natively
+ Copyright (C) 2005 Olaf Meeuwissen
+ Copyright (C) 2009 SEIKO EPSON CORPORATION
+
+ This file is part of the EPKOWA SANE backend.
+ It declares a wrapper around the backend's "Interpreter" interface.
+
+ The EPKOWA SANE backend is free software.
+ You can redistribute it and/or modify it under the terms of the GNU
+ General Public License as published by the Free Software Foundation;
+ either version 2 of the License or at your option any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+ FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+ See the GNU General Public License for more details.
+
+ You should have received a verbatim copy of the GNU General Public
+ License along with this program; if not, write to:
+
+ Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330
+ Boston, MA 02111-1307 USA
+
+ Linking the EPKOWA SANE backend statically or dynamically with
+ other modules is making a combined work based on the EPKOWA SANE
+ backend. Thus, the terms and conditions of the GNU General Public
+ License cover the whole combination.
+
+ As a special exception, the copyright holders of the EPKOWA SANE
+ backend give you permission to link the EPKOWA SANE backend with
+ independent modules that communicate with the EPKOWA SANE backend
+ solely through the "Interpreter" interface, regardless of the
+ license terms of these independent modules, and to copy and
+ distribute the resulting combined work under terms of your choice,
+ provided that every copy of the combined work is accompanied by a
+ complete copy of the source code of the EPKOWA SANE backend (the
+ version of the EPKOWA SANE backend used to produce the combined
+ work), being distributed under the terms of the GNU General Public
+ License plus this exception. An independent module is a module
+ which is not derived from or based on the EPKOWA SANE backend.
+
+ Note that people who make modified versions of the EPKOWA SANE
+ backend are not obligated to grant this special exception for their
+ modified versions; it is their choice whether to do so. The GNU
+ General Public License gives permission to release a modified
+ version without this exception; this exception also makes it
+ possible to release a modified version which carries forward this
+ exception.
+ */
+
+#ifndef epkowa_ip_h_included
+#define epkowa_ip_h_included
+
+#include <ltdl.h>
+
+#include "epkowa.h"
+#include "epkowa_ip_api.h"
+
+/* Insulate the interpreter_type declaration against changes.
+ */
+typedef channel device_type;
+
+
+/*! An object-like type for ESC/I interpreters.
+ */
+struct interpreter_type
+{
+ /* public members */
+
+ SANE_Status (*dtor) (device_type *device);
+
+ int (*open) (device_type *device);
+ int (*close) (device_type *device);
+
+ ssize_t (*recv) (device_type *device, void *buffer, size_t size,
+ SANE_Status *status);
+ ssize_t (*send) (device_type *device, const void *buffer, size_t size,
+ SANE_Status *status);
+
+ /* FIXME: very questionable public API for an _interpreter_ as this
+ seems to do some kind of image processing -> suggest to refactor
+ this out into a separate object */
+
+ SANE_Status (*free) (device_type *device);
+
+ SANE_Status (*ftor0) (device_type *device, SANE_Parameters *params,
+ SANE_Byte *ptr, SANE_Byte *end);
+ SANE_Status (*ftor1) (device_type *device, SANE_Parameters *params,
+ int depth, int left, int x_dpi, int optical_res);
+
+
+ /* private members */
+
+ device_type *_device;
+ void *_module;
+
+ int _tick_count;
+
+ double *_table;
+ SANE_Byte *_buffer;
+
+ /* hooks for all API declared in epkowa_ip_api.h */
+
+ bool (*_init) (int fd, io_callback *read, io_callback *write);
+ bool (*_init_with_ctrl) (int fd, io_callback *read, io_callback *write,
+ ctrl_callback *ctrl);
+ void (*_fini) (void);
+ int (*_read) (void *buf, size_t size);
+ int (*_write) (void *buf, size_t size);
+ void (*_power) (void);
+ int (*_s_0) (unsigned int offset, unsigned int width,
+ unsigned int resolution, unsigned int opt_resolution,
+ double *table);
+ void (*_s_1) (uint8_t *in_buf, uint8_t *out_buf,
+ unsigned int width, bool color, double *table);
+};
+
+/*! An abstract, factory-like ctor for interpreters.
+ */
+SANE_Status create_interpreter (device_type *device,
+ unsigned int usb_product_id);
+
+#endif /* !epkowa_ip_h_included */
diff --git a/backend/epkowa_ip_api.h b/backend/epkowa_ip_api.h
new file mode 100644
index 0000000..0e3f840
--- /dev/null
+++ b/backend/epkowa_ip_api.h
@@ -0,0 +1,92 @@
+/* epkowa_ip_api.h -- for scanners that don't speak EPSON ESC/I natively
+ Copyright (C) 2005 SEIKO EPSON CORPORATION
+
+ This file is part of the EPKOWA SANE backend.
+ It declares the backend's "Interpreter" interface.
+
+ The EPKOWA SANE backend is free software.
+ You can redistribute it and/or modify it under the terms of the GNU
+ General Public License as published by the Free Software Foundation;
+ either version 2 of the License or at your option any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+ FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+ See the GNU General Public License for more details.
+
+ You should have received a verbatim copy of the GNU General Public
+ License along with this program; if not, write to:
+
+ Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330
+ Boston, MA 02111-1307 USA
+
+ Linking the EPKOWA SANE backend statically or dynamically with
+ other modules is making a combined work based on the EPKOWA SANE
+ backend. Thus, the terms and conditions of the GNU General Public
+ License cover the whole combination.
+
+ As a special exception, the copyright holders of the EPKOWA SANE
+ backend give you permission to link the EPKOWA SANE backend with
+ independent modules that communicate with the EPKOWA SANE backend
+ solely through the "Interpreter" interface, regardless of the
+ license terms of these independent modules, and to copy and
+ distribute the resulting combined work under terms of your choice,
+ provided that every copy of the combined work is accompanied by a
+ complete copy of the source code of the EPKOWA SANE backend (the
+ version of the EPKOWA SANE backend used to produce the combined
+ work), being distributed under the terms of the GNU General Public
+ License plus this exception. An independent module is a module
+ which is not derived from or based on the EPKOWA SANE backend.
+
+ Note that people who make modified versions of the EPKOWA SANE
+ backend are not obligated to grant this special exception for their
+ modified versions; it is their choice whether to do so. The GNU
+ General Public License gives permission to release a modified
+ version without this exception; this exception also makes it
+ possible to release a modified version which carries forward this
+ exception.
+ */
+
+#ifndef epkowa_ip_api_h_included
+#define epkowa_ip_api_h_included
+
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "defines.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef ssize_t io_callback (void *buffer, size_t length);
+typedef ssize_t ctrl_callback (size_t request_type, size_t request,
+ size_t value, size_t index,
+ size_t size, void *buffer);
+
+bool int_init (int fd, io_callback *read, io_callback *write);
+bool int_init_with_ctrl (int fd, io_callback *read, io_callback *write,
+ ctrl_callback *ctrl);
+void int_fini (void);
+
+int int_read (void *buffer, size_t length);
+int int_write (void *buffer, size_t length);
+
+void int_power_saving_mode (void);
+
+int function_s_0 (unsigned int offset,
+ unsigned int width,
+ unsigned int resolution,
+ unsigned int opt_resolution,
+ double * table);
+
+void function_s_1 (uint8_t *in_buf, uint8_t *out_buf,
+ unsigned int width, bool color, double *table);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* !epkowa_ip_api_h_included */
diff --git a/backend/epkowa_scsi.c b/backend/epkowa_scsi.c
new file mode 100644
index 0000000..9132507
--- /dev/null
+++ b/backend/epkowa_scsi.c
@@ -0,0 +1,103 @@
+#include "epkowa_scsi.h"
+
+#ifdef HAVE_STDDEF_H
+#include <stddef.h>
+#endif
+
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+
+#ifdef NEED_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#include <string.h> /* for memset and memcpy */
+
+/*
+ * sense handler for the sanei_scsi_XXX comands
+ */
+SANE_Status
+sanei_epson_scsi_sense_handler (int scsi_fd, u_char * result, void *arg)
+{
+ /* to get rid of warnings */
+ scsi_fd = scsi_fd;
+ arg = arg;
+
+ if (result[0] && result[0] != 0x70)
+ {
+ log_data ("SCSI sense code = 0x%02x", result[0]);
+ return SANE_STATUS_IO_ERROR;
+ }
+ else
+ {
+ return SANE_STATUS_GOOD;
+ }
+}
+
+/*
+ *
+ *
+ */
+SANE_Status
+sanei_epson_scsi_inquiry (int fd, int page_code, void *buf, size_t * buf_size)
+{
+ u_char cmd[6];
+ int status;
+
+ memset (cmd, 0, 6);
+ cmd[0] = INQUIRY_COMMAND;
+ cmd[2] = page_code;
+ cmd[4] = *buf_size > 255 ? 255 : *buf_size;
+ status = sanei_scsi_cmd (fd, cmd, sizeof cmd, buf, buf_size);
+
+ return status;
+}
+
+/*
+ *
+ *
+ */
+int
+sanei_epson_scsi_read (int fd, void *buf, size_t buf_size,
+ SANE_Status * status)
+{
+ u_char cmd[6];
+
+ memset (cmd, 0, 6);
+ cmd[0] = READ_6_COMMAND;
+ cmd[2] = buf_size >> 16;
+ cmd[3] = buf_size >> 8;
+ cmd[4] = buf_size;
+
+ if (SANE_STATUS_GOOD ==
+ (*status = sanei_scsi_cmd (fd, cmd, sizeof (cmd), buf, &buf_size)))
+ return buf_size;
+
+ return 0;
+}
+
+/*
+ *
+ *
+ */
+int
+sanei_epson_scsi_write (int fd, const void *buf, size_t buf_size,
+ SANE_Status * status)
+{
+ u_char *cmd;
+
+ cmd = t_alloca (8 + buf_size, u_char);
+ memset (cmd, 0, 8);
+ cmd[0] = WRITE_6_COMMAND;
+ cmd[2] = buf_size >> 16;
+ cmd[3] = buf_size >> 8;
+ cmd[4] = buf_size;
+ memcpy (cmd + 8, buf, buf_size);
+
+ if (SANE_STATUS_GOOD ==
+ (*status = sanei_scsi_cmd2 (fd, cmd, 6, cmd + 8, buf_size, NULL, NULL)))
+ return buf_size;
+
+ return 0;
+}
diff --git a/backend/epkowa_scsi.h b/backend/epkowa_scsi.h
new file mode 100644
index 0000000..8328c76
--- /dev/null
+++ b/backend/epkowa_scsi.h
@@ -0,0 +1,28 @@
+#ifndef _EPSON_SCSI_H_
+#define _EPSON_SCSI_H_
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "epkowa.h"
+#include "sane/sanei_scsi.h"
+
+#define TEST_UNIT_READY_COMMAND (0x00)
+#define READ_6_COMMAND (0x08)
+#define WRITE_6_COMMAND (0x0a)
+#define INQUIRY_COMMAND (0x12)
+#define TYPE_PROCESSOR (0x03)
+
+#define INQUIRY_BUF_SIZE (36)
+
+SANE_Status sanei_epson_scsi_sense_handler (int scsi_fd, u_char * result,
+ void *arg);
+SANE_Status sanei_epson_scsi_inquiry (int fd, int page_code, void *buf,
+ size_t * buf_size);
+int sanei_epson_scsi_read (int fd, void *buf, size_t buf_size,
+ SANE_Status * status);
+int sanei_epson_scsi_write (int fd, const void *buf, size_t buf_size,
+ SANE_Status * status);
+
+#endif
diff --git a/backend/extension.h b/backend/extension.h
new file mode 100644
index 0000000..b8daad2
--- /dev/null
+++ b/backend/extension.h
@@ -0,0 +1,177 @@
+/* extension.h -- types to handle the various scanner document sources
+ * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifndef included_source_h
+#define included_source_h
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+
+/*! Base "class" for hardware extensions.
+
+ This "class" provides the common part of the other extension types.
+
+ \sa struct _fbf_extension, fbf_extension
+ \sa struct _adf_extension, adf_extension
+ \sa struct _tpu_extension, tpu_extension
+ */
+struct _extension
+{
+ SANE_Byte status;
+
+ SANE_Range x_range; /* in mm */
+ SANE_Range y_range;
+
+ SANE_Int max_x; /* in pixels */
+ SANE_Int max_y;
+
+ double doc_x; /* in mm */
+ double doc_y;
+ SANE_Bool has_size_check;
+};
+typedef struct _extension extension;
+
+
+/*! Flatbed hardware "extension".
+
+ \sa struct _extension, extension
+ */
+struct _fbf_extension
+{
+ SANE_Byte status;
+
+ SANE_Range x_range; /* in mm */
+ SANE_Range y_range;
+
+ SANE_Int max_x; /* in pixels */
+ SANE_Int max_y;
+
+ double doc_x; /* in mm */
+ double doc_y;
+ SANE_Bool has_size_check;
+};
+typedef struct _fbf_extension fbf_extension;
+
+
+/*! Auto Document Feeder (ADF) hardware extension.
+
+ \sa struct _extension, extension
+ */
+struct _adf_extension
+{
+ SANE_Byte status;
+
+ SANE_Range x_range; /* in mm */
+ SANE_Range y_range;
+
+ SANE_Int max_x; /* in pixels */
+ SANE_Int max_y;
+
+ double doc_x; /* in mm */
+ double doc_y;
+ SANE_Bool has_size_check;
+
+ SANE_Byte ext_status;
+
+ unsigned int sheet_count;
+ SANE_Bool using_duplex;
+
+ SANE_Bool auto_eject;
+};
+typedef struct _adf_extension adf_extension;
+
+
+/*! TransParency Unit (TPU) hardware extension.
+
+ \sa struct _extension, extension
+ */
+struct _tpu_extension
+{
+ SANE_Byte status;
+
+ SANE_Range x_range; /* in mm */
+ SANE_Range y_range;
+
+ SANE_Int max_x; /* in pixels */
+ SANE_Int max_y;
+
+ double doc_x; /* in mm */
+ double doc_y;
+ SANE_Bool has_size_check;
+
+ SANE_Bool has_focus;
+ SANE_Bool use_focus;
+};
+typedef struct _tpu_extension tpu_extension;
+
+
+/*! Convenience macro to test whether \a ext is currently being used.
+ */
+#define using(hw,ext) \
+ ((hw) && ((hw)->src == (const extension *) (hw)->ext))
+
+
+#endif /* !defined (included_source_h) */
diff --git a/backend/get-infofile.c b/backend/get-infofile.c
new file mode 100644
index 0000000..79d8c98
--- /dev/null
+++ b/backend/get-infofile.c
@@ -0,0 +1,277 @@
+/* get-infofile.c --
+ * Copyright (C) 2010 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: SEIKO EPSON CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#include <errno.h>
+#include <ltdl.h>
+#include <string.h>
+
+#include "get-infofile.h"
+#include "profile.c"
+
+const struct ScannerData scanner_data[] = {
+ {"GT-10000", 0x05, 0x05, {NULL, NULL}},
+ {"ES-6000", 0x05, 0x05, {NULL, NULL}},
+ {"Perfection610", 0x06, 0x01, {"Perfection 610", NULL}},
+ {"GT-6600", 0x06, 0x01, {NULL, NULL}},
+ {"Perfection1200", 0x07, 0x01, {"Perfection 1200", NULL}},
+ {"GT-7600", 0x07, 0x01, {NULL, NULL}},
+ {"Expression1600", 0x0D, 0x02, {"Expression 1600", NULL}},
+ {"ES-2000", 0x0D, 0x02, {NULL, NULL}},
+ {"Expression1640XL", 0x0F, 0x04, {"Expression 1640XL", NULL}},
+ {"ES-8500", 0x0F, 0x04, {NULL, NULL}},
+ {"Perfection640", 0x15, 0x01, {"Perfection 640", NULL}},
+ {"GT-6700", 0x15, 0x01, {NULL, NULL}},
+ {"Perfection1640", 0x16, 0x01, {"Perfection 1640", NULL}},
+ {"GT-8700", 0x16, 0x01, {NULL, NULL}},
+ {"Perfection1240", 0x18, 0x01, {"Perfection 1240", NULL}},
+ {"GT-7700", 0x18, 0x01, {NULL, NULL}},
+ {"GT-30000", 0x1A, 0x05, {NULL, NULL}},
+ {"ES-9000H", 0x1A, 0x05, {NULL, NULL}},
+ {"Expression1680", 0x1B, 0x02, {"Expression 1680", NULL}},
+ {"ES-2200", 0x1B, 0x02, {NULL, NULL}},
+ {"GT-7200", 0x1D, 0x01, {"Perfection 1250", "GT-7200"}},
+ {"GT-8200", 0x1F, 0x01, {"Perfection 1650", "GT-8200"}},
+ {"GT-9700", 0x21, 0x01, {"Perfection 2450", "GT-9700"}},
+ {"GT-7300", 0x23, 0x01, {"Perfection 1260", "GT-7300"}},
+ {"GT-8300", 0x25, 0x01, {"Perfection 1660", "GT-8300"}},
+ {"GT-9300", 0x27, 0x01, {"Perfection 2400", "GT-9300"}},
+ {"GT-9800", 0x29, 0x01, {"Perfection 3200", "GT-9800"}},
+ {"ES-7000H", 0x2B, 0x05, {"GT-15000", "ES-7000H"}},
+ {"LP-A500", 0x51, 0x01, {NULL, NULL}},
+ {"AL-CX11", 0x51, 0x01, {"AcuLaser CX11", NULL}},
+ {"GT-9400", 0x32, 0x06, {"Perfection 3170", "GT-9400"}},
+ {"CC-600PX", 0x2D, 0x01, {"Stylus CX5100/CX5200", "CC-600PX"}},
+ {"PM-A850", 0x3A, 0x01, {"Stylus Photo RX600", "PM-A850"}},
+ {"CX5400", 0x36, 0x01, {"Stylus CX5300/CX5400", NULL}},
+ {"GT-X700", 0x34, 0x01, {"Perfection 4870", "GT-X700"}},
+ {"RX500", 0x38, 0x01, {"Stylus Photo RX500/RX510", NULL}},
+ {"PX-A650", 0x37, 0x01, {"Stylus CX6300/CX6400", NULL}},
+ {"ES-10000G", 0x3F, 0x05, {NULL, NULL}},
+ {"Expression10000", 0x3F, 0x05, {"Expression 10000XL", NULL}},
+ {"CX4600", 0x46, 0x01, {"Stylus CX4500/CX4600", NULL}},
+ {"CX6600", 0x49, 0x01, {"Stylus CX6500/CX6600", NULL}},
+ {"CX3600", 0x46, 0x01, {"Stylus CX3500/CX3600/CX3650", "PX-A550"}},
+ {"RX420", 0x48, 0x01, {"Stylus Photo RX420/RX425/RX430", NULL}},
+ {"PM-A700", 0x48, 0x01, {NULL, NULL}},
+ {"PM-A870", 0x4B, 0x01, {"Stylus Photo RX620/RX630", "PM-A870"}},
+ {"GT-F500", 0x41, 0x06, {"Perfection 2480/2580", "GT-F500/F550"}},
+ {"GT-F600", 0x43, 0x06, {"Perfection 4180", "GT-F600"}},
+ {"PM-A900", 0x4D, 0x01, {"Stylus Photo RX700", "PM-A900"}},
+ {"GT-X800", 0x4F, 0x01, {"Perfection 4990", "GT-X800"}},
+ {"GT-X750", 0x54, 0x01, {"Perfection 4490", "GT-X750"}},
+ {"LP-M5500", 0x56, 0x05, {NULL, NULL}},
+ {"LP-M5600", 0x78, 0x05, {NULL, "LP-M5600"}},
+ {"GT-F520", 0x52, 0x01, {"Perfection 3490/3590", "GT-F520/F570"}},
+ {"CX3800", 0x57, 0x01, {"Stylus CX3700/CX3800/DX3800", NULL}},
+ {"CX7800", 0x5B, 0x01, {"Stylus CX7700/CX7800", NULL}},
+ {"PM-A750", 0x5D, 0x01, {"Stylus Photo RX520/RX530", "PM-A750"}},
+ {"CX4800", 0x58, 0x01, {"Stylus CX4700/CX4800/DX4800", "PX-A650"}},
+ {"CX4200", 0x59, 0x01, {"Stylus CX4100/CX4200/DX4200", NULL}},
+ {"PM-A950", 0x61, 0x01, {NULL, "PM-A950"}},
+ {"PM-A890", 0x5F, 0x01, {"Stylus Photo RX640/RX650", "PM-A890"}},
+ {"GT-X900", 0x63, 0x01, {"Perfection V700/V750", "GT-X900"}},
+ {"CX4000", 0x6B, 0x01, {"Stylus CX3900/DX4000", "PX-A620"}},
+ {"CX3000v", 0x6A, 0x01, {"Stylus CX2800/CX2900/ME 200", NULL}},
+ {"ES-H300", 0x65, 0x01, {"GT-2500", "ES-H300"}},
+ {"CX6000", 0x6C, 0x01, {"Stylus CX5900/CX6000/DX6000", "PX-A720"}},
+ {"PM-A820", 0x70, 0x01, {"Stylus Photo RX560/RX580/RX590", "PM-A820"}},
+ {"PM-A920", 0x71, 0x01, {NULL, "PM-A920"}},
+ {"PM-A970", 0x73, 0x01, {NULL, "PM-A970"}},
+ {"PM-T990", 0x75, 0x01, {NULL, "PM-T990"}},
+ {"CX5000", 0x77, 0x01, {"Stylus CX4900/CX5000/DX5000", NULL}},
+ {"GT-S600", 0x66, 0x01, {"Perfection V10/V100", "GT-S600/GT-F650"}},
+ {"GT-F700", 0x68, 0x01, {"Perfection V350", "GT-F700"}},
+ {"AL-CX21", 0x79, 0x01, {"AcuLaser CX21", NULL}},
+ {"GT-F670", 0x7A, 0x01, {"Perfection V200", "GT-F670"}},
+ {"GT-X770", 0x7C, 0x06, {"Perfection V500", "GT-X770"}},
+ {"CX4400", 0x7E, 0x01, {"Stylus CX4300/CX4400/CX5500/CX5600/DX4400", NULL}},
+ {"CX7400", 0x7F, 0x01, {"Stylus CX7300/CX7400/DX7400", "PX-A640"}},
+ {"CX8400", 0x80, 0x01, {"Stylus CX8300/CX8400/DX8400", "PX-A740"}},
+ {"CX9400Fax", 0x81, 0x01, {"Stylus CX9300F/CX9400Fax/DX9400F", "PX-FA700"}},
+ {"PM-T960", 0x82, 0x01, {NULL, "PM-T960"}},
+ {"PM-A940", 0x84, 0x01, {"Stylus Photo RX680/RX685/RX690", "PM-A940"}},
+ {"PM-A840", 0x85, 0x01, {"Stylus Photo RX585/RX595/RX610", "PM-A840/PM-A840S"}},
+ {"GT-D1000", 0x86, 0x01, {"GT-1500", "GT-D1000"}},
+ {"GT-X970", 0x87, 0x01, {NULL, NULL}},
+ {"LP-M5000", 0x97, 0x01, {NULL, NULL}},
+ {"LP-M6000", 0x89, 0x01, {NULL, NULL}},
+ {"ES-H7200", 0x8A, 0x05, {NULL, NULL}},
+ {"GT-20000", 0x8A, 0x05, {NULL, NULL}},
+ {"NX200", 0x8D, 0x01, {"Stylus NX200/SX200/TX200", NULL}},
+ {"NX400", 0x8E, 0x01, {"Stylus NX400/SX400/TX400", "PX-501A"}},
+ {"NX100", 0x93, 0x01, {"Stylus NX100/SX100/TX100/ME 300", "PX-401A"}},
+ {"NX300", 0x8F, 0x01, {"Stylus BX300F/TX300F/NX300/ME Office 600F", NULL}},
+ {"WorkForce 600", 0x90, 0x01, {"Stylus BX600FW/SX600FW/TX600FW/ME Office 700FW/WorkForce 600", "PX-601F"}},
+ {"Artisan 800", 0x91, 0x01, {"Stylus Photo PX800FW/TX800FW/Artisan 800", "EP-901A/EP-901F"}},
+ {"Artisan 700", 0x92, 0x01, {"Stylus Photo PX700W/TX700W/Artisan 700", "EP-801A"}},
+ {"WorkForce 500", 0x96, 0x01, {NULL, NULL}},
+ {"GT-F720", 0x8B, 0x01, {"Perfection V300", "GT-F720"}},
+ {"GT-S620", 0x8B, 0x01, {"Perfection V30", "GT-S620"}},
+ {"GT-S50", 0x00, 0x01, {"GT-S50", "ES-D200"}},
+ {"GT-S80", 0x00, 0x01, {"GT-S80", "ES-D400"}},
+ {"PID 0851", 0x98, 0x01, {"Stylus NX410/SX410/TX410 Series", NULL}},
+ {"PID 084D", 0x99, 0x01, {"Stylus NX110/SX110/TX110 Series", "PX-402A"}},
+ {"PID 084F", 0x9A, 0x01, {"Stylus NX210/SX210/TX210/ME OFFICE 510 Series", NULL}},
+ {"PID 0854", 0x9B, 0x01, {"Stylus Office TX510FN/BX310FN/WorkForce 310/ME OFFICE 650FN Series", NULL}},
+ {"PID 0856", 0x9C, 0x01, {"Stylus NX510/SX510W/TX550W Series", "PX-502A"}},
+ {"PID 0855", 0x9D, 0x01, {"Stylus Office TX610FW/BX610FW/SX610FW/WorkForce 610 Series", "PX-602F"}},
+ {"PID 0850", 0x9E, 0x01, {"Stylus Photo PX650/TX650 Series", "EP-702A"}},
+ {"PID 0852", 0xA0, 0x01, {"Stylus Photo TX710W/PX710W/Artisan 710 Series", "EP-802A"}},
+ {"PID 0853", 0x9F, 0x01, {"Stylus Photo PX810FW/Artisan 810 Series", "EP-902A"}},
+ {"GT-X820", 0xA1, 0x01, {"Perfection V600 Photo", "GT-X820"}},
+ {"GT-S55", 0x00, 0x01, {"GT-S55", NULL}},
+ {"GT-S85", 0x00, 0x01, {"GT-S85", "ES-D350"}},
+
+ /* array terminator */
+ {NULL, 0x00, 0x00, {NULL, NULL}},
+};
+
+
+static struct EpsonScanCommand scan_command[] = {
+ {0x01, 0, 0, ILLEGAL_CMD},
+ {0x02, ILLEGAL_CMD, 0, ILLEGAL_CMD},
+ {0x03, 0, ILLEGAL_CMD, ILLEGAL_CMD},
+ {0x04, ILLEGAL_CMD, ILLEGAL_CMD, ILLEGAL_CMD},
+ {0x05, 0, 0x19, ILLEGAL_CMD},
+ {0x06, 0, 0, '\f'},
+};
+
+static
+const scanner_data_t *
+get_scanner (const char *fw_name)
+{
+ const scanner_data_t *data = scanner_data;
+ require (data);
+ { /* input validation*/
+ if (!fw_name || 0 == strlen (fw_name))
+ return NULL;
+ }
+
+ while (data->fw_name != NULL /* end of array */
+ && (0 != strcmp (data->fw_name, fw_name)))
+ {
+ ++data;
+ }
+
+ if(!data->fw_name){
+ err_major("Unknown model name.");
+ return NULL;
+ }
+
+ return data;
+}
+
+char *
+get_scanner_data(const char* fw_name, const char *name)
+{
+ const scanner_data_t *data = get_scanner (fw_name);
+
+ if (!data) return NULL;
+
+ if(strcmp(name, FIRMWARE) == 0) return data->fw_name;
+ else if(strcmp(name, MODEL_OVERSEAS) == 0) return data->name.overseas;
+ else if(strcmp(name, MODEL_JAPAN) == 0) return data->name.japan;
+
+ return NULL;
+}
+
+struct EpsonScanCommand *get_scan_command(const char *fw_name)
+{
+ const scanner_data_t *data = get_scanner (fw_name);
+ scan_command_t *custom_command = NULL;
+
+ if (data && data->command_ID)
+ {
+ int id = data->command_ID - 1; /* adjust for offset */
+ require (id >= 0);
+ require ((unsigned) id < num_of (scan_command));
+
+ custom_command = &scan_command[id];
+ }
+ else
+ {
+ custom_command = &scan_command[0];
+ }
+
+ return custom_command;
+}
+
+EpsonScanHard get_epson_scan_hard (const char *fw_name)
+{
+ const scanner_data_t *data = get_scanner (fw_name);
+ EpsonScanHard profile = NULL;
+
+ int i = 0;
+
+ if (data && data->profile_ID)
+ {
+ i = num_of (_epson_scan_hard);
+
+ while (--i && data->profile_ID != epson_scan_hard[i].modelID)
+ ;
+ }
+ profile = (EpsonScanHard) &epson_scan_hard[i];
+
+ return profile;
+}
diff --git a/backend/get-infofile.h b/backend/get-infofile.h
new file mode 100644
index 0000000..d3daf80
--- /dev/null
+++ b/backend/get-infofile.h
@@ -0,0 +1,91 @@
+/* get-infofile.h --
+ * Copyright (C) 2010 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: SEIKO EPSON CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifndef included_get_infofile_h
+#define included_get_infofile_h
+
+#include "hw-data.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/*----------タグ----------------*/
+#define FIRMWARE "firmware name"
+#define MODEL_OVERSEAS "model overseas"
+#define MODEL_JAPAN "model japan"
+/*-----------------------------*/
+
+char *get_scanner_data(const char* fw_name, const char* name);
+
+struct EpsonScanCommand *get_scan_command(const char *fw_name);
+
+EpsonScanHard get_epson_scan_hard (const char *fw_name);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif
diff --git a/backend/hw-data.c b/backend/hw-data.c
new file mode 100644
index 0000000..2f8aa39
--- /dev/null
+++ b/backend/hw-data.c
@@ -0,0 +1,372 @@
+/* hw-data.c -- selected hardware specific data
+ * Copyright (C) 2004--2016 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "hw-data.h"
+
+#include <ctype.h>
+#include <locale.h>
+#include <stddef.h>
+#include <string.h>
+#include <time.h>
+
+
+#include "command.h"
+
+#include "utils.h"
+
+/* Not all scanners report the model name that is printed on the case.
+ Here we try to adjust for that by second guessing the name received
+ from the hardware with the aid of some environmental data.
+ WARNING: this is not and can not be foolproof.
+
+ The caller gets to manage the memory occupied by the string that is
+ returned.
+ */
+
+/*! Returns a copy of the F/W name of the device on the other end of a channel.
+ */
+char *
+get_fw_name (channel *ch)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+ const byte cmd[] = { ESC, 'f' };
+ char *fw_name = NULL;
+
+ if (!ch) return NULL;
+
+ channel_send (ch, cmd, num_of (cmd), &status);
+ if (SANE_STATUS_GOOD == status)
+ {
+ byte info[4];
+
+ channel_recv (ch, info, num_of (info), &status);
+ if (SANE_STATUS_GOOD == status)
+ {
+ char reply[42+1]; /* extra byte to enable string
+ termination */
+ channel_recv (ch, reply, num_of (reply) - 1, &status);
+ if (SANE_STATUS_GOOD == status)
+ {
+ char *lc_ctype = setlocale (LC_CTYPE, "C");
+ size_t n = num_of (reply);
+
+ do
+ {
+ --n, reply[n] = '\0';
+ }
+ while (26 < n && (isspace (reply[n-1]) || '\0' == reply[n-1]));
+ /* workaround for non-compliant
+ interpreter firmware names that
+ are padded with NULs and spaces
+ */
+ fw_name = strdup (reply + 26);
+ setlocale (LC_CTYPE, lc_ctype);
+ }
+ }
+ }
+ if (SANE_STATUS_GOOD != status)
+ err_minor ("%s", sane_strstatus (status));
+
+ /* Devices with USB product IDs 0x085C and 0x0883 may report the
+ * same firmware name. That breaks our data file finding scheme
+ * so we fix up the firmware name here to be spec compliant.
+ */
+ if (0 == strcmp_c ("PID 085C", fw_name)
+ && (CHAN_USB == ch->type && 0x0883 == ch->id))
+ {
+ strcpy (fw_name, "PID 0883");
+ }
+
+ return fw_name;
+}
+
+static SANE_Bool
+_is_listed (const char *needle, const char **haystack)
+{
+ if (!needle || !haystack) return SANE_FALSE;
+
+ while (*haystack)
+ {
+ if (0 == strcmp_c (needle, *haystack))
+ return SANE_TRUE;
+ ++haystack;
+ }
+ return SANE_FALSE;
+}
+
+
+SANE_Bool
+adf_needs_manual_centering (const device *hw)
+{
+ const char *fw_names[] = {
+ "LP-M6000",
+ "LP-M5000",
+ "LP-M5300",
+ "LP-M8040",
+ "LP-M8170",
+ "ES-H300",
+ "CX9400Fax",
+ "PID 087C",
+ "GT-S80",
+ "GT-S50",
+ "GT-S85",
+ "GT-S55",
+ NULL
+ };
+
+ require (using (hw, adf));
+ return _is_listed (hw->fw_name, fw_names);
+}
+
+
+SANE_Bool
+adf_has_auto_form_feed (const device *hw)
+{
+ const char *fw_names[] = {
+ "LP-M6000",
+ "LP-M5000",
+ "LP-M5300",
+ NULL
+ };
+
+ require (using (hw, adf));
+ return _is_listed (hw->fw_name, fw_names)
+ || (FSI_CAP_AFF & hw->fsi_cap_2);
+}
+
+SANE_Bool
+adf_early_paper_end_kills_scan (const device *hw)
+{
+ const char *fw_names[] = {
+ "ES-10000G",
+ "ES-7000H",
+ "ES-H7200",
+ "Expression10000",
+ "GT-20000",
+ NULL,
+ };
+
+ require (using (hw, adf));
+ return _is_listed (hw->fw_name, fw_names);
+}
+
+SANE_Bool
+push_button_is_black_listed (const device *hw)
+{
+ const char *fw_names[] = {
+ "LP-M6000",
+ "LP-M5000",
+ "LP-M5300",
+ NULL,
+ };
+
+ // whitelist of scanners that support push button via the network
+ const char *fw_names_net[] = {
+ "ES-H7200",
+ "GT-20000",
+ NULL,
+ };
+
+
+ return _is_listed (hw->fw_name, fw_names)
+ || hw->uses_locking
+ || (CHAN_NET == hw->channel->type &&
+ !_is_listed (hw->fw_name, fw_names_net));
+}
+
+SANE_Int
+large_res_kills_adf_scan (const device *hw)
+{
+ const char *fw_names1[] = {
+ "ES-H300",
+ "LP-M6000",
+ "LP-M5000",
+ "LP-M5300",
+ "LP-M8040",
+ "LP-M8170",
+ NULL,
+ };
+
+ const char *fw_names2[] = {
+ "NX300",
+ "WorkForce 600",
+ "Artisan 800",
+ NULL,
+ };
+
+ require (hw->adf);
+ if (_is_listed (hw->fw_name, fw_names1)) return 600;
+ if (_is_listed (hw->fw_name, fw_names2)) return 1200;
+
+ return 0;
+}
+SANE_Bool
+zoom_kills_adf_scan (const device *hw)
+{
+ const char *fw_names[] = {
+ "LP-M6000",
+ "LP-M5000",
+ "LP-M5300",
+ NULL,
+ };
+
+ require (hw->adf);
+ return _is_listed (hw->fw_name, fw_names);
+}
+
+/* Say whether duplex scans scan front and back sides in the same
+ * direction. This is definitely the case for single pass duplex
+ * devices (which scan front and back simultaneously) but is only
+ * seldomly encountered with double pass duplexers (which scan the
+ * front and back one after another).
+ */
+SANE_Bool
+adf_duplex_direction_matches (const device *hw)
+{
+ const char *fw_names[] = {
+ "GT-S80",
+ "GT-S50",
+ "GT-S85",
+ "GT-S55",
+ NULL,
+ };
+
+ return hw->adf && _is_listed (hw->fw_name, fw_names);
+}
+
+/*! Return an override for the current source's max_y value (in pixels)
+ * to be used with autocropping. In case no override is needed, zero
+ * is returned.
+ *
+ * A number of models support scanning a slightly taller document than
+ * the firmware would have you believe. This "feature" is referred to
+ * as overscanning and used to minimize the risks of chopping off bits
+ * from skewed originals when performing autocropping.
+ *
+ * \todo Allow for model specific max_y values.
+ */
+SANE_Int
+autocrop_max_y (const device *hw)
+{
+ SANE_Int rv = 0;
+
+ const char *fw_names[] = {
+ "GT-S80",
+ "GT-S50",
+ "GT-S85",
+ "GT-S55",
+ NULL,
+ };
+
+ if (_is_listed (hw->fw_name, fw_names))
+ {
+ if (using (hw, adf))
+ {
+ rv = 15 * hw->base_res;
+ }
+ }
+ return rv;
+}
+
+/* Restrict functionality to tested devices only.
+ */
+SANE_Bool
+enable_dip_deskew (const device *hw)
+{
+ const char *fw_names[] = {
+ "GT-S80",
+ "GT-S50",
+ "GT-S85",
+ "GT-S55",
+ NULL,
+ };
+
+ return _is_listed (hw->fw_name, fw_names);
+}
+
+SANE_Bool
+push_button_needs_polling (const device *hw)
+{
+ const char *fw_names[] = {
+ "DS-30",
+ NULL,
+ };
+
+ return _is_listed (hw->fw_name, fw_names);
+}
+
+SANE_Bool
+maintenance_is_supported (const device *hw)
+{
+ const char *fw_names[] = {
+ "DS-30",
+ NULL,
+ };
+
+ return _is_listed (hw->fw_name, fw_names);
+}
diff --git a/backend/hw-data.h b/backend/hw-data.h
new file mode 100644
index 0000000..9ead25a
--- /dev/null
+++ b/backend/hw-data.h
@@ -0,0 +1,136 @@
+/* hw-data.h -- selected hardware specific data
+ * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifndef hw_data_h_included
+#define hw_data_h_included
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "device.h"
+
+struct ScannerName
+{
+ char *overseas;
+ char *japan;
+};
+typedef struct ScannerName scanner_name_t;
+
+struct ScannerData
+{
+ char *fw_name;
+ int profile_ID;
+ int command_ID;
+ const scanner_name_t name;
+};
+typedef struct ScannerData scanner_data_t;
+
+#define ILLEGAL_CMD 0xFF
+
+struct EpsonScanCommand
+{
+ int command_ID;
+ unsigned char set_focus_position;
+ unsigned char feed;
+ unsigned char eject;
+
+ bool lock;
+ bool unlock;
+};
+typedef struct EpsonScanCommand scan_command_t;
+
+struct CapabilityData
+{
+ char *option;
+ char *mode;
+
+ long width;
+ long height;
+ long base;
+};
+typedef struct CapabilityData capability_data_t;
+
+char * get_fw_name (channel *ch);
+
+SANE_Bool adf_early_paper_end_kills_scan (const device *hw);
+SANE_Bool adf_has_auto_form_feed (const device *hw);
+SANE_Bool adf_needs_manual_centering (const device *hw);
+SANE_Bool push_button_is_black_listed (const device *hw);
+SANE_Int large_res_kills_adf_scan (const device *hw);
+SANE_Bool zoom_kills_adf_scan (const device *hw);
+SANE_Bool adf_duplex_direction_matches (const device *hw);
+SANE_Int autocrop_max_y (const device *hw);
+SANE_Bool enable_dip_deskew (const device *hw);
+SANE_Bool push_button_needs_polling (const device *hw);
+SANE_Bool maintenance_is_supported (const device *hw);
+
+/*! Array with colour correction profiles.
+
+ \todo Replace this with a data member in the scanner that gets
+ initialized with data read from file.
+ */
+extern const EpsonScanHardRec *epson_scan_hard;
+
+#endif /* !defined (hw_data_h_included) */
diff --git a/backend/ipc.c b/backend/ipc.c
new file mode 100644
index 0000000..e1d6437
--- /dev/null
+++ b/backend/ipc.c
@@ -0,0 +1,563 @@
+/* ipc.c -- inter-process communication (IPC) support
+ * Copyright (C) 2019 SEIKO EPSON Corporation
+ *
+ * License: EPSON END USER SOFTWARE LICENSE
+ * Author : SEIKO EPSON Corporation
+ *
+ * This file is part of Image Scan! for Linux.
+ * It is distributed under the terms of the EPSON END USER SOFTWARE LICENSE.
+ *
+ * You should have received a verbatim copy of the EPSON END USER SOFTWARE
+ * LICENSE along with the software.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ipc.h"
+
+#include <errno.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "defines.h"
+#include "message.h"
+
+
+/*! Attempts to read all the data up to \a size bytes.
+ * MERR is returned if an error, such as a timeout, occurs.
+ * MEOF is returned if EOF is reached.
+ * Otherwise, returns the number of bytes read, which must be greater than 0.
+ */
+static ssize_t
+recv_all (int fd, void *buf, size_t size)
+{
+ ssize_t n = 0;
+ ssize_t t = 1;
+
+ if (0 == size) return MERR;
+
+ while (n < size && t > 0)
+ {
+ errno = 0;
+ t = read (fd, buf + n, size - n);
+ if (0 > t)
+ {
+ err_major ("read failed: %s", strerror (errno));
+ return MERR;
+ }
+ else
+ {
+ n += t;
+ log_call ("transferred %zd bytes, total %zd/%zd", t, n, size);
+ }
+ if (0 == t) return MEOF;
+ }
+
+ return n;
+}
+
+/*! Attempts to write all the data up to \a size bytes.
+ * MERR is returned if an error, such as a timeout, occurs.
+ * Otherwise, returns the number of bytes written.
+ * A return value of zero indicates that nothing was written.
+ */
+static ssize_t
+send_all (int fd, const void *buf, size_t size)
+{
+ ssize_t n = 0;
+ ssize_t t = 1;
+
+ if (0 == size) return MERR;
+
+ while (n < size && t > 0)
+ {
+ errno = 0;
+ t = write (fd, buf + n, size - n);
+ if (0 > t)
+ {
+ err_major ("write failed: %s", strerror (errno));
+ return MERR;
+ }
+ else
+ {
+ n += t;
+ log_call ("transferred %zd bytes, total %zd/%zd", t, n, size);
+ }
+ }
+
+ return n;
+}
+
+/*! MERR is returned if an error, such as a timeout, occurs.
+ * MERR is also returned if writing the ipc header failed.
+ * Otherwise, the number of bytes of the payload that were successfully
+ * written is returned.
+ * A return value of zero indicates that nothing was written, this should
+ * only occur when the payload is of zero size.
+ */
+ssize_t
+ipc_send (int sock, uint16_t id, uint8_t type_status,
+ size_t size, const void* payload)
+{
+ ssize_t n = 0;
+
+ n = send_all (sock, &id, sizeof (id));
+ if (0 >= n) return MERR;
+
+ n = send_all (sock, &type_status, sizeof (type_status));
+ if (0 >= n) return MERR;
+
+ n = send_all (sock, &size, sizeof (size));
+ if (0 >= n) return MERR;
+
+ if (0 == size) return 0;
+ if (!payload) return MERR;
+
+ n = send_all (sock, payload, size);
+
+ log_info ("send packet {key: %d, msg: 0x%02x, size: %zd}",
+ id, type_status, size);
+
+ if (ENABLE_DEBUG && 0 < n)
+ {
+ if (MSG_DBG_IMG_THRESHOLD < n)
+ dbg_img (payload, n);
+ else
+ dbg_hex (payload, n);
+ }
+
+ return n;
+}
+
+/*! Caller must have allocated space for \a id, and \a type_status.
+ * \a payload is automatically allocated based on the size field of the
+ * ipcling header, and it is the responsibility of the caller to
+ * deallocate it.
+ *
+ * MERR is returned if an error, such as a timeout, occurs.
+ * MEOF is returned if EOF is reached.
+ * Otherwise, returns the number of bytes read.
+ * A return value of zero is valid, as ipc packets do not necessarily
+ * have to contain a payload.
+ */
+ssize_t
+ipc_recv (int sock, uint16_t *id, uint8_t *type_status,
+ void** payload)
+{
+ size_t size = 0;
+ char* buf = NULL;
+ ssize_t n = 0;
+
+ n = recv_all (sock, id, sizeof (*id));
+ if (0 > n) return n;
+
+ n = recv_all (sock, type_status, sizeof (*type_status));
+ if (0 > n) return n;
+
+ n = recv_all (sock, &size, sizeof (size));
+ if (0 > n) return n;
+
+ if (0 == size) return 0;
+ if (!payload) return MERR;
+
+ buf = t_malloc (size, char);
+ if (!buf) return MERR;
+
+ n = recv_all (sock, buf, size);
+
+ *payload = buf;
+
+ log_info ("recv packet {key: %d, msg: 0x%02x, size: %zd}",
+ *id, *type_status, size);
+
+ if (ENABLE_DEBUG && 0 < n)
+ {
+ if (MSG_DBG_IMG_THRESHOLD < n)
+ dbg_img (*payload, n);
+ else
+ dbg_hex (*payload, n);
+ }
+
+ return n;
+}
+
+/*! \brief Does the real work of starting the \a child process
+ */
+static
+SANE_Status
+ipc_fork (process *child)
+{
+ SANE_Status s = SANE_STATUS_GOOD;
+
+ int pipe_fd[2];
+
+ require (child);
+
+ if (-1 == pipe (pipe_fd))
+ {
+ err_fatal ("pipe: %s", strerror (errno));
+ return SANE_STATUS_ACCESS_DENIED;
+ }
+
+ child->pid = fork ();
+ if (0 == child->pid)
+ {
+ /* replace child process with a plugin program
+ */
+ close (pipe_fd[0]); /* unused read end */
+ if (0 <= dup2 (pipe_fd[1], STDOUT_FILENO))
+ {
+ log_info ("%s[%d]: starting", child->name, getpid ());
+ if (-1 == execl (child->name, child->name, NULL))
+ {
+ err_fatal ("%s[%d]: %s", child->name, getpid (),
+ strerror (errno));
+ }
+ }
+ else
+ {
+ err_major ("%s[%d]: %s", child->name, getpid (),
+ strerror (errno));
+ }
+
+ /* notify the parent process that we're done here */
+ write (pipe_fd[1], "-1\n", strlen ("-1\n"));
+ fsync (pipe_fd[1]);
+
+ close (pipe_fd[1]);
+ exit (EXIT_FAILURE);
+ }
+
+ if (0 > child->pid)
+ {
+ err_fatal ("fork: %s", strerror (errno));
+ s = SANE_STATUS_CANCELLED;
+ }
+ else
+ {
+ /* Check whether child process has (unexpectedly) exited. We
+ don't want to have zombies in our closet ;-)
+ */
+ pid_t w = waitpid (child->pid, NULL, WNOHANG);
+ if (-1 == w)
+ {
+ err_minor ("waitpid: %s", strerror (errno));
+ }
+ if (0 != w)
+ {
+ log_info ("%s[%d]: exited prematurely", child->name, child->pid);
+ s = SANE_STATUS_CANCELLED;
+ }
+ else
+ {
+ FILE *fp = fdopen (pipe_fd[0], "rb");
+ if (fp)
+ {
+ if (1 != fscanf (fp, "%d", &(child->port)))
+ {
+ err_major ("fscanf: %s", strerror (errno));
+ }
+ fclose (fp);
+ }
+ else
+ {
+ err_fatal ("%s", strerror (errno));
+ }
+ }
+ }
+ close (pipe_fd[0]);
+ close (pipe_fd[1]);
+
+ if (0 > child->port)
+ s = SANE_STATUS_CANCELLED;
+
+ return s;
+}
+
+/*! \brief Requests a connection to a \a child
+ */
+static
+SANE_Status
+ipc_connect (process *child)
+{
+ struct sockaddr_in addr;
+ struct timeval t;
+ int rv;
+
+ require (child);
+
+ log_call ("(%s, %d)", child->name, child->port);
+
+ errno = 0;
+ child->socket = socket (AF_INET, SOCK_STREAM, 0);
+ if (0 > child->socket)
+ {
+ err_major ("socket: %s", strerror (errno));
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ t.tv_sec = 30;
+ t.tv_usec = 0;
+ errno = 0;
+ rv = setsockopt (child->socket, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof (t));
+ if (0 > rv)
+ {
+ err_minor ("socket option: %s", strerror (errno));
+ }
+
+ errno = 0;
+ rv = setsockopt (child->socket, SOL_SOCKET, SO_SNDTIMEO, &t, sizeof (t));
+ if (0 > rv)
+ {
+ err_minor ("socket option: %s", strerror (errno));
+ }
+
+ memset (&addr, 0, sizeof (addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons (child->port);
+ addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
+
+ if (0 != connect (child->socket, (struct sockaddr *) &addr, sizeof (addr)))
+ {
+ err_major ("connect: %s", strerror (errno));
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+process *
+ipc_exec (const char *program, const char *pkglibdir, SANE_Status *status)
+{
+ SANE_Status s = SANE_STATUS_GOOD;
+ process *child = NULL;
+
+ log_call ("(%s, %s, %p)", program, pkglibdir, status);
+
+ child = t_malloc (1, process);
+ if (!child)
+ {
+ if (status) *status = SANE_STATUS_NO_MEM;
+ return NULL;
+ }
+
+ child->pid = -1;
+ child->port = -1;
+ child->socket = -1;
+ child->name = NULL;
+
+ if (!pkglibdir)
+ {
+ child->name = strdup (program);
+ }
+ else
+ {
+ int n = (strlen (pkglibdir) + strlen (FILE_SEP_STR)
+ + strlen (program) + 1);
+ char *name = t_malloc (n, char);
+
+ if (name)
+ {
+ sprintf (name, "%s%s%s", pkglibdir, FILE_SEP_STR, program);
+ child->name = name;
+ }
+ }
+
+ if (!child->name)
+ {
+ s = SANE_STATUS_NO_MEM;
+ }
+ else if (access (child->name, X_OK))
+ {
+ s = SANE_STATUS_ACCESS_DENIED;
+ }
+
+ if (SANE_STATUS_GOOD != s)
+ {
+ if (status) *status = s;
+ delete (child);
+ return NULL;
+ }
+
+ s = ipc_fork (child);
+ if (SANE_STATUS_GOOD == s)
+ {
+ int tries = 5;
+
+ do
+ {
+ if (SANE_STATUS_GOOD != s)
+ sleep (1);
+ s = ipc_connect (child);
+ }
+ while (0 < --tries && SANE_STATUS_GOOD != s);
+ }
+
+ if (SANE_STATUS_GOOD != s)
+ {
+ child = ipc_kill (child);
+ promise (!child);
+ }
+ else
+ {
+ promise (child);
+ promise (0 < child->pid);
+ promise (0 < child->port);
+ promise (0 < child->socket);
+ promise (child->name);
+ }
+
+ if (status) *status = s;
+
+ return child;
+}
+
+process *
+ipc_kill (process *child)
+{
+ log_call ("(%p)", child);
+
+ if (child)
+ {
+ int status = 0;
+
+ log_info ("terminating %s (port %d)", child->name, child->port);
+
+ if (0 <= child->socket)
+ {
+ if (0 != close (child->socket))
+ {
+ err_minor ("%s", strerror (errno));
+ }
+ }
+ if (1 < child->pid)
+ {
+ if (0 != kill (child->pid, SIGHUP))
+ {
+ err_minor ("%s", strerror (errno));
+ }
+ if (child->pid != waitpid (child->pid, &status, 0))
+ {
+ err_major ("%s", strerror (errno));
+ }
+
+ if (!WIFSIGNALED (status))
+ {
+ err_major ("%s[%d]: went off the deep end!",
+ child->name, child->pid);
+ }
+ else
+ {
+ if (SIGHUP != WTERMSIG (status))
+ {
+ err_major ("%s[%d]: %s", child->name, child->pid,
+ strsignal (WTERMSIG (status)));
+ }
+ }
+ }
+
+ const_delete (child->name, char *);
+ delete (child);
+ }
+
+ return child;
+}
+
+void
+ipc_dip_proc (process *child, int flag, const ipc_dip_parms *p,
+ SANE_Parameters *ctx, void **buffer)
+{
+ int socket;
+ uint8_t status = STATUS_NG;
+ uint16_t id = 0;
+ ssize_t n;
+
+ require (child);
+ socket = child->socket;
+
+ require (TYPE_DIP_SKEW_FLAG == flag || TYPE_DIP_CROP_FLAG == flag);
+ require (0 < socket && p && ctx && buffer && *buffer);
+
+ /* inter-process procedure call, status will be STATUS_NG in case
+ * anything goes wrong during IPC call sequence
+ */
+ {
+ n = ipc_send (socket, id, flag | TYPE_DIP_CTOR,
+ strlen (p->fw_name), p->fw_name);
+ if (strlen (p->fw_name) == n)
+ {
+ n = ipc_recv (socket, &id, &status, NULL);
+ if (STATUS_OK == status)
+ {
+ if (sizeof (*p) != ipc_send (socket, id, flag | TYPE_DIP_PARM,
+ sizeof (*p), p))
+ {
+ status = STATUS_NG;
+ }
+ else
+ {
+ ipc_recv (socket, &id, &status, NULL);
+ if (STATUS_OK == status)
+ {
+ ssize_t size = ctx->bytes_per_line * ctx->lines;
+
+ if (size != ipc_send (socket, id, flag | TYPE_DIP_DATA,
+ size, *buffer))
+ {
+ err_minor ("image truncated");
+ status = STATUS_NG;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (STATUS_NG == status) /* abort further processing */
+ {
+ ipc_send (socket, id, flag | TYPE_DIP_DTOR, 0, NULL);
+ ipc_recv (socket, &id, &status, NULL);
+ return;
+ }
+
+ /* acquire DIP results */
+ {
+ uint8_t req = flag | TYPE_DIP_PARM;
+ void *buf = NULL;
+ ipc_dip_parms par;
+
+ if (sizeof (par) == ipc_recv (socket, &id, &req, &buf))
+ {
+ ssize_t size;
+
+ memcpy (&par, buf, sizeof (par));
+ size = par.parms.bytes_per_line * par.parms.lines;
+
+ req = flag | TYPE_DIP_DATA;
+ delete (buf);
+
+ if (size == ipc_recv (socket, &id, &req, &buf))
+ {
+ memcpy (ctx, &par.parms, sizeof (*ctx));
+ delete (*buffer);
+ *buffer = buf;
+ }
+ else
+ {
+ err_minor ("image truncated");
+ delete (buf);
+ }
+ }
+ }
+ ipc_send (socket, id, flag | TYPE_DIP_DTOR, 0, NULL);
+ ipc_recv (socket, &id, &status, NULL);
+}
diff --git a/backend/ipc.h b/backend/ipc.h
new file mode 100644
index 0000000..46d2047
--- /dev/null
+++ b/backend/ipc.h
@@ -0,0 +1,121 @@
+/* ipc.h -- inter-process communication (IPC) support
+ * Copyright (C) 2019 SEIKO EPSON Corporation
+ *
+ * License: EPSON END USER SOFTWARE LICENSE
+ * Author : SEIKO EPSON Corporation
+ *
+ * This file is part of Image Scan! for Linux.
+ * It is distributed under the terms of the EPSON END USER SOFTWARE LICENSE.
+ *
+ * You should have received a verbatim copy of the EPSON END USER SOFTWARE
+ * LICENSE along with the software.
+ */
+
+
+#ifndef ipc_h
+#define ipc_h
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <sane/sane.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/*! \brief Exceptional return values from ipc_recv() and ipc_send()
+ * In particular, a return value of zero from read() conflicts with
+ * our ipc_recv() API where a return value of zero does not indicate
+ * EOF, but a successful read of zero bytes.
+ */
+#define MERR -1 /*!< read/write error */
+#define MEOF -2 /*!< eof; only returned by ipc_recv() */
+
+enum {
+ TYPE_ESC = 1,
+ TYPE_INIT,
+ TYPE_DIE,
+ TYPE_OPEN,
+ TYPE_CLOSE,
+ TYPE_LIST,
+ TYPE_STATUS,
+
+ TYPE_DIP_CTOR = 0x01,
+ TYPE_DIP_DTOR = 0x02,
+ TYPE_DIP_PARM = 0x03,
+ TYPE_DIP_DATA = 0x04,
+ TYPE_DIP_MASK = 0x0f,
+ TYPE_DIP_FLAG = 0xf0,
+
+ TYPE_DIP_SKEW_FLAG = 0x10,
+ TYPE_DIP_SKEW_CTOR = TYPE_DIP_SKEW_FLAG | TYPE_DIP_CTOR,
+ TYPE_DIP_SKEW_DTOR = TYPE_DIP_SKEW_FLAG | TYPE_DIP_DTOR,
+ TYPE_DIP_SKEW_PARM = TYPE_DIP_SKEW_FLAG | TYPE_DIP_PARM,
+ TYPE_DIP_SKEW_DATA = TYPE_DIP_SKEW_FLAG | TYPE_DIP_DATA,
+ TYPE_DIP_SKEW_MASK = TYPE_DIP_SKEW_FLAG | TYPE_DIP_MASK,
+
+ TYPE_DIP_CROP_FLAG = 0x20,
+ TYPE_DIP_CROP_CTOR = TYPE_DIP_CROP_FLAG | TYPE_DIP_CTOR,
+ TYPE_DIP_CROP_DTOR = TYPE_DIP_CROP_FLAG | TYPE_DIP_DTOR,
+ TYPE_DIP_CROP_PARM = TYPE_DIP_CROP_FLAG | TYPE_DIP_PARM,
+ TYPE_DIP_CROP_DATA = TYPE_DIP_CROP_FLAG | TYPE_DIP_DATA,
+ TYPE_DIP_CROP_MASK = TYPE_DIP_CROP_FLAG | TYPE_DIP_MASK,
+};
+
+enum {
+ STATUS_OK = 0,
+ STATUS_NG
+};
+
+ssize_t ipc_send (int sock, uint16_t id, uint8_t type_status,
+ size_t size, const void* payload);
+ssize_t ipc_recv (int sock, uint16_t *id, uint8_t *type_status,
+ void** payload);
+
+ typedef struct
+ {
+ pid_t pid;
+ int port;
+ int socket;
+
+ const char *name;
+
+ } process;
+
+ /*! \brief Attempts to start \a program as a child process
+ */
+ process * ipc_exec (const char *program, const char *pkglibdir,
+ SANE_Status *status);
+
+ /*! \brief Terminates a child process
+ */
+ process * ipc_kill (process *child);
+
+ typedef struct
+ {
+ SANE_Parameters parms;
+ SANE_Int res_x;
+ SANE_Int res_y;
+ SANE_Int gamma;
+ SANE_Bool bside;
+ char fw_name[16 + 1];
+
+ } ipc_dip_parms;
+
+ /*! \brief Performs an image processing action
+ *
+ * If any of the IPC messaging signals an error, the original image
+ * data will not be modified at all. That is, \a ctx and \a buffer
+ * remain unchanged in such a case.
+ */
+ void ipc_dip_proc (process *child, int flag, const ipc_dip_parms *p,
+ SANE_Parameters *ctx, void **buffer);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* !defined (ipc_h) */
diff --git a/backend/list.c b/backend/list.c
new file mode 100644
index 0000000..7e0810c
--- /dev/null
+++ b/backend/list.c
@@ -0,0 +1,198 @@
+/* list.c -- A poor implementation of a linked list
+ * Copyright (C) 2008 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+/*! \file
+ \brief Implements a linked list.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "list.h"
+
+#include <string.h>
+
+/*! Create a new list.
+ */
+list* list_create ()
+{
+ list* lst = 0;
+
+ lst = t_calloc (1, list);
+ return lst;
+}
+
+/*! Destroy a list.
+ * Deletes the list related structures, and the contained data with the
+ * given destructor function. If a destructor is not provided the data is
+ * left as is.
+ */
+void list_destroy (list* lst, void (*dtor)(void*))
+{
+ if (!lst) return;
+
+ list_entry* entry = lst->head;
+ list_entry* tmp;
+
+ while (entry != NULL)
+ {
+ if (dtor && entry->data)
+ (*dtor) (entry->data);
+
+ tmp = entry->next;
+ delete (entry);
+ entry = tmp;
+ }
+
+ delete (lst);
+}
+
+/*! Adds a new element to the end of the list.
+ * Does *not* make a copy.
+ */
+bool list_append (list* lst, void* new_data)
+{
+ if (!lst) return false;
+
+ list_entry* entry = 0;
+
+ entry = t_calloc (1, list_entry);
+ if (!entry) return false;
+
+ entry->data = new_data;
+
+ if (0 == lst->num_entries)
+ {
+ lst->head = entry;
+ lst->tail = entry;
+ lst->cur = entry;
+ }
+ else
+ {
+ lst->tail->next = entry;
+ lst->tail = entry;
+ }
+
+ lst->num_entries += 1;
+ return true;
+}
+
+/*! Creates a NULL terminated array of pointers to list entries.
+ */
+void** list_normalize (const list *lst)
+{
+ void **nlst;
+
+ if (!lst) return NULL;
+
+ nlst = t_malloc (lst->num_entries + 1, void *);
+ if (nlst)
+ {
+ list *p = (list *) lst;
+ list_entry *cur = p->cur;
+
+ void *entry;
+ int i = 0;
+
+ list_reset (p);
+ while ((entry = list_next (p)))
+ {
+ nlst[i++] = entry;
+ }
+ nlst[i++] = NULL;
+ p->cur = cur;
+ }
+ return nlst;
+}
+
+/*! Obtain the number of elements in the list
+ */
+size_t list_size (list* lst)
+{
+ if (!lst) return 0;
+ return lst->num_entries;
+}
+
+/*! Reset iteration to start from the beginning of the list
+ */
+void list_reset (list* lst)
+{
+ if (!lst) return;
+
+ lst->cur = lst->head;
+}
+
+/*! Proceed to the next element in the list.
+ * Used to iterate over the list items.
+ */
+void* list_next (list* lst)
+{
+ if (!lst || !lst->cur) return 0;
+
+ void* rv = lst->cur->data;
+ lst->cur = lst->cur->next;
+
+ return rv;
+}
diff --git a/backend/list.h b/backend/list.h
new file mode 100644
index 0000000..f7547f6
--- /dev/null
+++ b/backend/list.h
@@ -0,0 +1,117 @@
+/* list.h -- A poor implementation of a linked list
+ * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+#ifndef list_h_included
+#define list_h_included
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "defines.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+typedef struct list_entry
+{
+ void* data;
+ struct list_entry* next;
+} list_entry;
+
+typedef struct list
+{
+ list_entry* head; /* first element of the list */
+ list_entry* tail; /* last element of the list */
+ list_entry* cur; /* current element of the list */
+ size_t num_entries; /* number of elements in the list */
+} list;
+
+/* create a new empty list */
+list* list_create ();
+
+/* destroy a list */
+void list_destroy (list* lst, void (*dtor)(void*));
+
+/* add one element to the end of the list */
+bool list_append (list* lst, void* new_data);
+
+/* convert a list to a flat array */
+void** list_normalize (const list* lst);
+
+/* number of elements in the list */
+size_t list_size (list* lst);
+
+/* reset iteration to start from the beginning of the list*/
+void list_reset (list* lst);
+
+/* proceed to the next element in the list */
+void* list_next (list* lst);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* !defined (list_h_included) */
diff --git a/backend/message.c b/backend/message.c
new file mode 100644
index 0000000..67853d4
--- /dev/null
+++ b/backend/message.c
@@ -0,0 +1,152 @@
+/* message.c -- consistent error, progress and debugging feedback
+ * Copyright (C) 2019 SEIKO EPSON Corporation
+ *
+ * License: EPSON END USER SOFTWARE LICENSE
+ * Author : SEIKO EPSON Corporation
+ *
+ * This file is part of Image Scan! for Linux.
+ * It is distributed under the terms of the EPSON END USER SOFTWARE LICENSE.
+ *
+ * You should have received a verbatim copy of the EPSON END USER SOFTWARE
+ * LICENSE along with the software.
+ */
+
+/*! \file
+ \brief Infra-structure to provide consistent backend feedback.
+
+ \todo Describe purpose of the three message categories and their
+ levels. Also document usage policy and message formatting
+ conventions.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "message.h"
+
+#if ENABLE_DEBUG
+
+unsigned long msg_level = 0;
+
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <strings.h>
+
+/*! Initialises the message infra-structure.
+
+ This function sets level at which the backend should produce
+ feedback. The value is gotten from the \c SANE_DEBUG_EPKOWA
+ environment variable.
+
+ The following, case insensitive string literals are supported
+ (in increasing level of feedback):
+
+ - \c FATAL
+ - \c MAJOR
+ - \c MINOR
+ - \c INFO
+ - \c CALL
+ - \c DATA
+ - \c CMD
+ - \c HEX
+ - \c IMG
+
+ \todo Add support for decimal literal level specification.
+ */
+void
+msg_init (void)
+{
+ struct level_def
+ {
+ const char *key;
+ msg_level_type val;
+ };
+
+ const struct level_def def[] =
+ {
+ {"FATAL", ERR_FATAL},
+ {"MAJOR", ERR_MAJOR},
+ {"MINOR", ERR_MINOR},
+
+ {"INFO" , LOG_INFO},
+ {"CALL" , LOG_CALL},
+ {"DATA" , LOG_DATA},
+
+ {"CMD" , DBG_CMD},
+ {"HEX" , DBG_HEX},
+ {"IMG" , DBG_IMG},
+
+ {NULL} /* array terminator */
+ };
+
+ const char *level = getenv ("SANE_DEBUG_EPKOWA");
+ const struct level_def *p = def;
+
+ msg_level = 0;
+
+ if (!level) return;
+
+ while (p && p->key)
+ {
+ if (0 == strcasecmp (level, p->key))
+ {
+ msg_level = p->val;
+ log_info ("setting message level to '%s' (%d)",
+ p->key, p->val);
+ return;
+ }
+ ++p;
+ }
+}
+
+
+/*! Dumps the contents of a \a buffer in hexadecimal format.
+ */
+void
+msg_dump (const char *fmt, const void *buffer, size_t sz)
+{
+ const size_t quad_length = 4;
+ const size_t quad_count = 4;
+ const size_t line_length = quad_length * quad_count;
+
+ const unsigned char *buf = buffer;
+
+ char ascii[line_length + 1];
+ size_t i = 0;
+
+ ascii[line_length] = '\0';
+
+ while (i < sz)
+ {
+ if (0 == i % line_length) /* header */
+ fprintf (stderr, "%s%08zx: ", fmt, i);
+
+ ascii[i % line_length] = (isprint (buf[i]) ? buf[i] : '.');
+
+ fprintf (stderr, " %02x", buf[i]);
+ ++i;
+ if (0 == i % quad_length) /* spacer */
+ fprintf (stderr, " ");
+ if (0 == i % line_length) /* trailer */
+ fprintf (stderr, " |%s|\n", ascii);
+ }
+
+ if (0 != i % line_length) /* last line */
+ {
+ do
+ { /* align trailer */
+ ascii[i % line_length] = ' ';
+ fprintf (stderr, " ");
+ ++i;
+ if (0 == i % quad_length)
+ fprintf (stderr, " ");
+ }
+ while (0 != i % line_length);
+ fprintf (stderr, " |%s|\n", ascii);
+ }
+}
+
+#endif /* ENABLE_DEBUG */
diff --git a/backend/message.h b/backend/message.h
new file mode 100644
index 0000000..622f2a1
--- /dev/null
+++ b/backend/message.h
@@ -0,0 +1,191 @@
+/* message.h -- consistent error, progress and debugging feedback
+ * Copyright (C) 2019 SEIKO EPSON Corporation
+ *
+ * License: EPSON END USER SOFTWARE LICENSE
+ * Author : SEIKO EPSON Corporation
+ *
+ * This file is part of Image Scan! for Linux.
+ * It is distributed under the terms of the EPSON END USER SOFTWARE LICENSE.
+ *
+ * You should have received a verbatim copy of the EPSON END USER SOFTWARE
+ * LICENSE along with the software.
+ */
+
+
+#ifndef message_h
+#define message_h
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#ifndef ENABLE_DEBUG
+#define ENABLE_DEBUG 0
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+
+ typedef enum
+ {
+ ERR_FATAL = (1 << 0),
+ ERR_MAJOR = (1 << 1),
+ ERR_MINOR = (1 << 2),
+
+ LOG_INFO = (1 << 3),
+ LOG_CALL = (1 << 4),
+ LOG_DATA = (1 << 5),
+
+ DBG_CMD = (1 << 6),
+ DBG_HEX = (1 << 7),
+ DBG_IMG = (1 << 8),
+ }
+ msg_level_type;
+
+
+ extern unsigned long msg_level;
+
+
+ /*! \brief Maximum buffer size not considered to be image data
+ *
+ * This value can be used by low-level I/O functions that want to
+ * log the interesting data they handle but lack the knowledge to
+ * distinguish between that data and boring old image bytes.
+ */
+#define MSG_DBG_IMG_THRESHOLD 512
+
+
+#define FMT_FILE __FILE__ ":%d: "
+#define FMT_LINE , __LINE__
+#define FMT_MODULE FMT_FILE "[%s]"
+
+#define FMT_FATAL FMT_MODULE "[F] "
+#define FMT_MAJOR FMT_MODULE "[M] "
+#define FMT_MINOR FMT_MODULE "[m] "
+#define FMT_INFO FMT_MODULE "{I} "
+#define FMT_CALL FMT_MODULE "{C} " "%s "
+#define FMT_DATA FMT_MODULE "{D} "
+#define FMT_CMD FMT_MODULE "(e) "
+#define FMT_HEX "[" MSG_MODULE "]" "(x) "
+#define FMT_IMG "[" MSG_MODULE "]" "(i) "
+
+#if ENABLE_DEBUG
+
+#define err_fatal(fmt,arg...) \
+ do \
+ { \
+ if (ERR_FATAL <= msg_level) \
+ fprintf (stderr, FMT_FATAL fmt "\n" FMT_LINE, MSG_MODULE, \
+ ## arg); \
+ } \
+ while (0) \
+ /**/
+
+#define err_major(fmt,arg...) \
+ do \
+ { \
+ if (ERR_MAJOR <= msg_level) \
+ fprintf (stderr, FMT_MAJOR fmt "\n" FMT_LINE, MSG_MODULE, \
+ ## arg); \
+ } \
+ while (0) \
+ /**/
+
+#define err_minor(fmt,arg...) \
+ do \
+ { \
+ if (ERR_MINOR <= msg_level) \
+ fprintf (stderr, FMT_MINOR fmt "\n" FMT_LINE, MSG_MODULE, \
+ ## arg); \
+ } \
+ while (0) \
+ /**/
+
+#define log_info(fmt,arg...) \
+ do \
+ { \
+ if (LOG_INFO <= msg_level) \
+ fprintf (stderr, FMT_INFO fmt "\n" FMT_LINE, MSG_MODULE, \
+ ## arg); \
+ } \
+ while (0) \
+ /**/
+
+#define log_call(fmt,arg...) \
+ do \
+ { \
+ if (LOG_CALL <= msg_level) \
+ fprintf (stderr, FMT_CALL fmt "\n" FMT_LINE, MSG_MODULE, \
+ __func__, ## arg); \
+ } \
+ while (0) \
+ /**/
+
+#define log_data(fmt,arg...) \
+ do \
+ { \
+ if (LOG_DATA <= msg_level) \
+ fprintf (stderr, FMT_DATA fmt "\n" FMT_LINE, MSG_MODULE, \
+ ## arg); \
+ } \
+ while (0) \
+ /**/
+
+#define dbg_cmd(buf,sz) \
+ do \
+ { \
+ if (DBG_CMD <= msg_level) \
+ fprintf (stderr, FMT_CMD fmt "\n" FMT_LINE, MSG_MODULE, \
+ ## arg); \
+ } \
+ while (0) \
+ /**/
+
+#define dbg_hex(buf,sz) \
+ do \
+ { \
+ if (DBG_HEX <= msg_level) \
+ msg_dump (FMT_HEX, buf, sz); \
+ } \
+ while (0) \
+ /**/
+
+#define dbg_img(buf,sz) \
+ do \
+ { \
+ if (DBG_IMG <= msg_level) \
+ msg_dump (FMT_IMG, buf, sz); \
+ } \
+ while (0) \
+ /**/
+
+ void msg_init (void);
+ void msg_dump (const char *, const void *, size_t);
+
+#else /* !ENABLE_DEBUG */
+
+#define err_fatal(fmt,arg...)
+#define err_major(fmt,arg...)
+#define err_minor(fmt,arg...)
+#define log_info(fmt,arg...)
+#define log_call(fmt,arg...)
+#define log_data(fmt,arg...)
+#define dbg_cmd(buf,sz)
+#define dbg_hex(buf,sz)
+#define dbg_img(buf,sz)
+
+#define msg_init()
+#define msg_dump(fmt,buf,sz)
+
+#endif /* !ENABLE_DEBUG */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* !defined (message_h) */
diff --git a/backend/model-info.c b/backend/model-info.c
new file mode 100644
index 0000000..5423483
--- /dev/null
+++ b/backend/model-info.c
@@ -0,0 +1,707 @@
+/* model-info.c -- per model information objects and cache
+ * Copyright (C) 2010 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "model-info.h"
+
+#include <stdlib.h>
+#include <time.h>
+
+#include "list.h"
+#include "message.h"
+#include "utils.h"
+
+#include "get-infofile.h"
+#include "xmlreader.h"
+
+
+/*! \brief Points to our model info cache.
+ *
+ * Its value is guaranteed to be \c NULL \e outside the scope of a
+ * model_info_cache_init()/model_info_cache_exit() pair.
+ */
+static list *_cache = NULL;
+
+
+/*! \brief Directory to search for resource files
+ *
+ * The _model_info_ctor() needs this.
+ */
+static const char *_datadir = NULL;
+
+
+/*! \brief Collects all (supported) model information
+ */
+typedef struct
+{
+ char *fw_name; /* key for _model_info_cache_get_info */
+ char *overseas; /* model name */
+ char *japan; /* model name */
+ char *name; /* points to one of overseas, japan or
+ * fw_name, never NULL */
+ scan_command_t *command; /* command customisation info */
+ EpsonScanHard profile; /* colour profiles */
+
+ bool from_file; /* origin of our data, used to control
+ * which resources need to be released
+ * at destruction time */
+
+ capability_data_t *dfault;
+ capability_data_t *adf_duplex;
+} _model_info_t;
+
+typedef enum
+{
+ FEED,
+ FOCUS,
+ EJECT,
+
+} _command_id_t;
+
+/* Model info cache implementation details */
+static _model_info_t * _model_info_cache_get_info (const char *fw_name,
+ SANE_Status *status);
+
+/* Model info implementation details */
+static _model_info_t * _model_info_ctor (const char *fw_name,
+ SANE_Status *status);
+static void _model_info_dtor (void *p);
+static char * _model_info_guess_name (const _model_info_t *self);
+static SANE_Status _model_info_merge_file (_model_info_t *self);
+static void _model_info_merge_data (_model_info_t *self,
+ xmlNodePtr node);
+
+static bool _model_info_set_cmd (const _model_info_t *self,
+ unsigned char *cmd, _command_id_t id);
+
+
+/*! \brief Sets up model info cache support.
+ *
+ * Model specific information will be looked for in \a pkgdatadir.
+ *
+ * \returns An opaque pointer to the cache. An additional \a status
+ * will be returned as well if the argument is not \c NULL.
+ */
+void *
+model_info_cache_init (const char *pkgdatadir, SANE_Status *status)
+{
+ SANE_Status s = SANE_STATUS_GOOD;
+
+ log_call ("(%s, %p)", pkgdatadir, status);
+ require (pkgdatadir);
+
+ if (_cache)
+ {
+ err_minor ("been here, done that");
+ if (0 != strcmp_c (_datadir, pkgdatadir))
+ {
+ err_major ("already using %s", _datadir);
+ }
+ if (status) *status = s;
+ return _cache;
+ }
+
+ _datadir = strdup (pkgdatadir);
+ _cache = list_create ();
+ if (!_datadir || !_cache)
+ {
+ _cache = model_info_cache_exit (_cache);
+
+ s = SANE_STATUS_NO_MEM;
+ }
+
+ if (0 != atexit (xmlCleanupParser))
+ {
+ err_minor ("could not register XML parser cleanup function");
+ }
+
+ /* ?FIXME?
+ * Check for existence/readability of _pkgdatadir and log its
+ * absence/presence. It is _not_ fatal if the directory does
+ * not exist or is not readable. We just want to note it via
+ * an err_minor(). If not readable, we may want to return a
+ * SANE_STATUS_ACCESS_DENIED.
+ */
+
+ if (status) *status = s;
+ return _cache;
+}
+
+
+/*! \brief Tears down model info cache support.
+ *
+ * Releases all resources associated with the model info cache.
+ * The \a self argument should be an opaque pointer obtained via a
+ * call to model_info_cache_init().
+ *
+ * Note that for error recovery purposes, model_info_cache_init() may
+ * call this function with an empty _cache or no cache at all.
+ *
+ * \returns \c NULL, always
+ */
+void *
+model_info_cache_exit (void *self)
+{
+ log_call ("(%p)", self);
+ require (_cache == self);
+
+ const_delete (_datadir, char *);
+ list_destroy (_cache, _model_info_dtor);
+ _datadir = NULL;
+ _cache = NULL;
+
+ promise (!_cache);
+
+ return _cache;
+}
+
+
+/*! \brief Attempts to find model information for \a fw_name.
+ *
+ * \return A pointer to the model information, \c NULL if not found.
+ */
+const void *
+model_info_cache_get_info (const char *fw_name, SANE_Status *status)
+{
+ /* Just forward to the internal implementation. The return
+ * statement will handle the pointer type conversion.
+ */
+ return _model_info_cache_get_info (fw_name, status);
+}
+
+
+/*! \brief Returns a best-effort model name based on \a fw_name.
+ *
+ * The caller gets to manage the memory occupied by the string that
+ * is returned. Note that \c NULL may be returned.
+ */
+char *
+model_info_cache_get_model (const char *fw_name)
+{
+ SANE_Status s = SANE_STATUS_GOOD;
+ _model_info_t *m = NULL;
+
+ log_call ("(%s)", fw_name);
+ require (_cache && _datadir);
+
+ if (!fw_name || 0 == strlen (fw_name))
+ {
+ err_minor ("%s", sane_strstatus (SANE_STATUS_INVAL));
+ return strdup ("(unknown model)");
+ }
+
+ m = _model_info_cache_get_info (fw_name, &s);
+ if (!m)
+ {
+ err_minor ("%s", sane_strstatus (s));
+ return strdup (fw_name); /* best we can do */
+ }
+
+ return strdup (m->name);
+}
+
+
+/*! \brief Returns a reference to the model name.
+ *
+ * Resources associated with the reference are owned by \a self. The
+ * caller should \e not attempt to release them.
+ */
+const char *
+model_info_get_name (const void *self)
+{
+ require (self); /* ?FIXME? check if in _cache? */
+
+ return ((const _model_info_t *) self)->name;
+}
+
+
+/*! \brief Returns a reference to the model's colour profiles.
+ *
+ * Resources associated with the reference are owned by \a self. The
+ * caller should \e not attempt to release them.
+ */
+const EpsonScanHard
+model_info_get_profile (const void *self)
+{
+ require (self); /* ?FIXME? check if in _cache? */
+
+ return ((const _model_info_t *) self)->profile;
+}
+
+
+/*! \brief Modify selected commands in the \a cmd specification
+ *
+ * This function caters to quirks in the command level specification
+ * reported by the device. Especially commands for hardware options
+ * may be affected.
+ *
+ * \return \c true if commands have been modified, \c false otherwise
+ */
+bool
+model_info_customise_commands (const void *self, EpsonCmd cmd)
+{
+ bool customised = false;
+ _model_info_t *self_ = NULL;
+
+ require (self); /* ?FIXME? check if in _cache? */
+
+ if (!cmd)
+ {
+ err_minor ("%s", sane_strstatus (SANE_STATUS_INVAL));
+ return customised;
+ }
+
+ self_ = (_model_info_t *) self;
+ customised |= _model_info_set_cmd (self_, &cmd->set_focus_position, FOCUS);
+ customised |= _model_info_set_cmd (self_, &cmd->feed, FEED);
+ customised |= _model_info_set_cmd (self_, &cmd->eject, EJECT);
+
+ return customised;
+}
+
+
+/*! \brief Attempts to find model information for \a fw_name.
+ *
+ * Checks for existing information in the cache before it attempts to
+ * add new model information. Takes care to preserve the cache's cur
+ * member so as not to invalidate existing "iterators".
+ *
+ * \return A pointer to the model information. \c NULL if not found,
+ * in case anything went wrong trying to add the info to the
+ * cache or the caller passed in garbage.
+ */
+static _model_info_t *
+_model_info_cache_get_info (const char *fw_name, SANE_Status *status)
+{
+ SANE_Status s = SANE_STATUS_GOOD;
+ _model_info_t *info = NULL;
+ list_entry *cur = NULL;
+ bool found = false;
+
+ log_call ("(%s)", fw_name);
+ require (_cache && _datadir);
+
+ if (!fw_name || 0 == strlen (fw_name))
+ {
+ if (status) *status = SANE_STATUS_INVAL;
+ return NULL;
+ }
+
+ cur = _cache->cur; /* check whether cached */
+ list_reset (_cache);
+ while (!found && (info = list_next (_cache)))
+ {
+ found = (0 == strcmp_c (info->fw_name, fw_name));
+ }
+ _cache->cur = cur;
+
+ if (!found) /* try to add info to cache */
+ {
+ info = _model_info_ctor (fw_name, &s);
+ if (!info || !list_append (_cache, info))
+ {
+ _model_info_dtor (info);
+ info = NULL;
+ }
+ }
+
+ if (status) *status = s;
+ return info;
+}
+
+
+/*! \brief Creates and initialises a model info object.
+ *
+ * \return A valid model info object or \c NULL if unable to acquire
+ * the necessary memory resources.
+ */
+static _model_info_t *
+_model_info_ctor (const char *fw_name, SANE_Status *status)
+{
+ SANE_Status s = SANE_STATUS_GOOD;
+ _model_info_t *self = NULL;
+
+ log_call ("(%s)", fw_name);
+ require (fw_name);
+
+ self = t_calloc (1, _model_info_t);
+ if (!self)
+ {
+ if (status) *status = SANE_STATUS_NO_MEM;
+ return NULL;
+ }
+
+ self->fw_name = strdup (fw_name);
+ if (!self->fw_name)
+ {
+ _model_info_dtor (self);
+ if (status) *status = SANE_STATUS_NO_MEM;
+ return NULL;
+ }
+
+ /* Set defaults using data defined in the source code. The various
+ * getters decide a decent default in case self->fw_name is not one
+ * of the names for which we have data in our sources.
+ */
+ self->overseas = get_scanner_data (self->fw_name, MODEL_OVERSEAS);
+ self->japan = get_scanner_data (self->fw_name, MODEL_JAPAN);
+ self->profile = get_epson_scan_hard (self->fw_name);
+ self->command = get_scan_command (self->fw_name);
+
+ self->from_file = false;
+
+ s = _model_info_merge_file (self);
+
+ self->name = _model_info_guess_name (self);
+
+ if (self) /* make sure things are compliant */
+ {
+ promise (self->fw_name && self->name);
+ promise ( self->name == self->fw_name
+ || self->name == self->overseas
+ || self->name == self->japan);
+ promise (self->profile);
+ promise (self->command);
+ }
+ if (status) *status = s;
+ return self;
+}
+
+
+/*! \brief Destroys a model object.
+ *
+ * Releases any resources acquired throughout the object's life time.
+ */
+static void
+_model_info_dtor (void *self)
+{
+ _model_info_t *p = (_model_info_t *) self;
+
+ if (!p) return;
+
+ if (p->from_file)
+ {
+ /* :FIXME: p->profile may have been acquired as several
+ * individual arrays at construction. Check!
+ */
+ if (p->profile != get_epson_scan_hard (p->fw_name))
+ delete (p->profile);
+ if (p->command != get_scan_command (p->fw_name))
+ delete (p->command);
+ delete (p->overseas);
+ delete (p->japan);
+ if(p->dfault)
+ {
+ delete (p->dfault->option);
+ delete (p->dfault->mode);
+ }
+ delete (p->dfault);
+ if (p->adf_duplex)
+ {
+ delete (p->adf_duplex->option);
+ delete (p->adf_duplex->mode);
+ }
+ delete (p->adf_duplex);
+ }
+
+ delete (p->fw_name);
+ delete (p);
+}
+
+/*! \brief Returns a best effort guess for model name on the device
+ *
+ * This functions implements the policy used to decide which of the
+ * various model names \c self->name should point to. The function
+ * is intended for use at construction of \a self.
+ */
+static char *
+_model_info_guess_name (const _model_info_t *self)
+{
+ require (self);
+
+ if (self->japan && self->overseas)
+ {
+ time_t lt = time (NULL);
+ struct tm *ptr = localtime (&lt);
+
+ if (ptr && 0 == strncmp_c ("JST", ptr->tm_zone, 3))
+ {
+ return self->japan;
+ }
+ else
+ {
+ return self->overseas;
+ }
+ }
+
+ if (self->japan) return self->japan;
+ if (self->overseas) return self->overseas;
+ return self->fw_name;
+}
+
+static char *
+get_path_name (const char *hex_name, char *path_name, size_t path_size)
+{
+ char *path;
+ size_t size = snprintf (path_name, path_size, "%s%s%s%s",
+ _datadir, FILE_SEP_STR, hex_name,
+ ".xml");
+
+ if (size > -1 || size < path_size)
+ return path_name;
+
+ if (size > -1)
+ path_size = size + 1;
+ else
+ path_size *= 2;
+
+ if (0 == path_size)
+ path_size = 512;
+
+ path = t_realloc (path_name, path_size, char);
+ if (!path)
+ {
+ delete (path_name);
+ return NULL;
+ }
+ path_name = path;
+
+ return get_path_name (hex_name, path_name, path_size);
+}
+
+/*! \brief Attempts to merge model information from a data file.
+ */
+static SANE_Status
+_model_info_merge_file (_model_info_t *self)
+{
+ xmlDocPtr doc = NULL;
+
+ char *path_name = NULL;
+ char *hex_name = NULL;
+
+ require (self);
+
+ hex_name = fw_name_to_hex (self->fw_name);
+
+ if (!hex_name) return SANE_STATUS_NO_MEM;
+
+ path_name = get_path_name (hex_name, NULL, 0);
+ delete (hex_name); /* no longer needed */
+
+ if (!path_name) return SANE_STATUS_NO_MEM;
+
+ log_data ("%s", path_name);
+
+ doc = xmlReadFile (path_name, NULL,
+ XML_PARSE_NOERROR | XML_PARSE_NOWARNING);
+ delete (path_name); /* no longer needed */
+
+ if (doc)
+ {
+ _model_info_merge_data (self, xmlDocGetRootElement (doc));
+ xmlFreeDoc (doc);
+ }
+ else
+ {
+ xmlErrorPtr p = xmlGetLastError ();
+ if (p) err_minor ("%s", p->message);
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+static void
+_model_info_merge_data (_model_info_t *self, xmlNodePtr node)
+{
+ /* Define the XML element tags that we are able to merge.
+ */
+ const xmlChar *device = (xmlChar *) "device";
+ const xmlChar *profiles = (xmlChar *) "profile-set";
+ const xmlChar *commands = (xmlChar *) "command-set";
+ const xmlChar *capabilities = (xmlChar *) "capabilities";
+ const xmlChar *cap_option = (xmlChar *) "option";
+ const xmlChar *cap_mode = (xmlChar *) "mode";
+ char *tmp;
+
+ require (self);
+
+ if (!node) return;
+
+ node = node->xmlChildrenNode;
+ while (node)
+ {
+ if (!xmlIsBlankNode (node))
+ {
+ if (0 == xmlStrcmp (node->name, device))
+ {
+ char *tmp = NULL;
+
+ tmp = parseDevices (node, MODEL_OVERSEAS);
+ if (tmp) self->overseas = tmp;
+
+ tmp = parseDevices (node, MODEL_JAPAN);
+ if (tmp) self->japan = tmp;
+ }
+ else if (0 == xmlStrcmp (node->name, profiles))
+ {
+ EpsonScanHard profile = parseProfiles (node);
+ if (profile) self->profile = profile;
+ }
+ else if (0 == xmlStrcmp (node->name, commands))
+ {
+ scan_command_t *command = parseCommands_set (node);
+ if (command) self->command = command;
+ }
+ else if (0 == xmlStrcmp (node->name, capabilities))
+ {
+ tmp = (char *)xmlGetProp(node, (const xmlChar *) cap_mode);
+ if(strcmp_c(tmp, "duplex") == 0){
+ capability_data_t *capability = parseCapabilities (node);
+ if (capability) {
+ self->adf_duplex = capability;
+ self->adf_duplex->option = (char *)xmlGetProp(node, (const xmlChar *) cap_option);
+ self->adf_duplex->mode = tmp;
+ }
+ }else {
+ capability_data_t *capability = parseCapabilities (node);
+ if (capability) {
+ self->dfault = capability;
+ self->dfault->option = (char *)xmlGetProp(node, (const xmlChar *) cap_option);
+ self->dfault->mode = tmp;
+ }
+ }
+ }
+ }
+ node = node->next;
+ }
+ self->from_file = true;
+}
+
+/*! \brief Customises a single command
+ *
+ * \return \c true if \a cmd was modified, \c false otherwise
+ */
+static bool
+_model_info_set_cmd (const _model_info_t *self, unsigned char *cmd,
+ _command_id_t id)
+{
+ unsigned char cmd_ = ILLEGAL_CMD;
+
+ require (self && cmd);
+
+ if (FEED == id) cmd_ = self->command->feed;
+ if (FOCUS == id) cmd_ = self->command->set_focus_position;
+ if (EJECT == id) cmd_ = self->command->eject;
+
+ if (ILLEGAL_CMD != cmd_)
+ {
+ *cmd = cmd_;
+ return true;
+ }
+ return false;
+}
+
+bool
+model_info_has_lock_commands (const void *self)
+{
+ _model_info_t *self_ = NULL;
+
+ require (self);
+
+ self_ = (_model_info_t *) self;
+
+ return (self_->command->lock && self_->command->unlock);
+}
+
+scan_area_t
+model_info_max_scan_area(const void *self, const char *option, const char *mode)
+{
+ _model_info_t *self_ = NULL;
+ scan_area_t scan_area;
+
+ require (self);
+ require (option);
+
+ scan_area.width = SANE_FIX(-1);
+ scan_area.height = SANE_FIX(-1);
+
+ self_ = (_model_info_t *) self;
+
+ if(strcmp(option, "adf") == 0 && strcmp_c(mode, "duplex") == 0){
+ if(self_->adf_duplex){
+ scan_area.width = SANE_FIX (self_->adf_duplex->width * MM_PER_INCH / self_->adf_duplex->base);
+ scan_area.height = SANE_FIX (self_->adf_duplex->height * MM_PER_INCH / self_->adf_duplex->base);
+ }
+ }else {
+ if(self_->dfault){
+ scan_area.width = SANE_FIX (self_->dfault->width * MM_PER_INCH / self_->dfault->base);
+ scan_area.height = SANE_FIX (self_->dfault->height * MM_PER_INCH / self_->dfault->base);
+ }
+ }
+
+ return scan_area;
+}
diff --git a/backend/model-info.h b/backend/model-info.h
new file mode 100644
index 0000000..b92defb
--- /dev/null
+++ b/backend/model-info.h
@@ -0,0 +1,145 @@
+/* model-info.h -- per model information objects and cache
+ * Copyright (C) 2010 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifndef model_info_h_included
+#define model_info_h_included
+
+/*! \file
+ * \brief Model specific information.
+ *
+ * There is a fair bit of device information that does not change
+ * during the life-time of the device. The ESC/I scanner protocol
+ * provides support to query the device to get a significant part of
+ * this information. However, some of the information we would like
+ * to use is not available from the device. In addition, the device
+ * sometimes returns incorrect information.
+ *
+ * For a long time we have been working around this by putting the
+ * additional information and the corrections directly in our code.
+ * The model info support provided here implements a unified API to
+ * the device information allowing us to move it out of the code and
+ * into per model resource files.
+ *
+ * The API aims for on-demand, cached data retrieval. In order to
+ * support that, the sane_init() and sane_exit() functions need to
+ * handle cache initialisation and clean-up. They can do so through
+ * the model_info_cache_init() and model_cache_exit() functions.
+ *
+ * The sane_get_devices() and sane_open() functions can get access to
+ * per model information through the model_info_cache_get_info() API
+ * and use its return value in calls to the model info accessors. Of
+ * course, other SANE API entries may do so as well, though we cannot
+ * think of a good need for them to do so (as the information can be
+ * stored in the SANE_Handle during sane_open()).
+ *
+ * The number of model info accessors is still quite limited but we
+ * expect that to change when the implementation proceeds.
+ *
+ * There is also convenience API meant to make initialisation of the
+ * SANE_Device structure marginally less verbose.
+ */
+
+#include <sane/sane.h>
+
+#include "defines.h"
+#include "device.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ struct ScanArea
+ {
+ SANE_Fixed width;
+ SANE_Fixed height;
+ };
+ typedef struct ScanArea scan_area_t;
+
+ /* Model info cache creation and destruction */
+ void * model_info_cache_init (const char *pkgdatadir, SANE_Status *status);
+ void * model_info_cache_exit (void *self);
+
+ /* Model info cache accessors */
+ const void * model_info_cache_get_info (const char *fw_name,
+ SANE_Status *status);
+
+ /* Model info cache convenience methods */
+ char * model_info_cache_get_model (const char *fw_name);
+ /* ?FIXME? add convenience methods for vendor and type? */
+
+ /* Model info accessors */
+ const char * model_info_get_name (const void *self);
+ const EpsonScanHard model_info_get_profile (const void *self);
+ bool model_info_customise_commands (const void *self, EpsonCmd cmd);
+ bool model_info_has_lock_commands (const void *self);
+
+ scan_area_t model_info_max_scan_area(const void *self, const char *option, const char *mode);
+ /* :FIXME: add more accessors */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* !defined (model_info_h_included) */
diff --git a/backend/net-obj.c b/backend/net-obj.c
new file mode 100644
index 0000000..d5ee958
--- /dev/null
+++ b/backend/net-obj.c
@@ -0,0 +1,123 @@
+/* net-obj.c --
+ * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "net-obj.h"
+
+#include "defines.h"
+#include "ipc.h"
+#include "message.h"
+
+const char *net_prog_name = "network";
+
+static process *net = NULL;
+
+void *
+net_init (const char *pkglibdir, SANE_Status *status)
+{
+ log_call ("(%s, %p)", pkglibdir, status);
+
+ if (net)
+ {
+ err_minor ("been here, done that");
+ if (status) *status = SANE_STATUS_GOOD;
+ return net;
+ }
+
+ if (!pkglibdir) return NULL;
+
+ net = ipc_exec (net_prog_name, pkglibdir, status);
+
+ return net;
+}
+
+void *
+net_exit (void *self)
+{
+ log_call ("(%p)", self);
+ require (net == self);
+
+ if (net)
+ {
+ net = ipc_kill (net);
+ promise (!net);
+ }
+
+ return net;
+}
+
+/*! Obtain the socket connected to the network plugin
+ */
+int
+net_get_sock (void *self)
+{
+ log_call ("(%p)", self);
+ require (net == self);
+
+ if (net) return net->socket;
+ return -1;
+}
diff --git a/backend/net-obj.h b/backend/net-obj.h
new file mode 100644
index 0000000..e336626
--- /dev/null
+++ b/backend/net-obj.h
@@ -0,0 +1,89 @@
+/* net-obj.h --
+ * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifndef net_obj_h_included
+#define net_obj_h_included
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sane/sane.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+
+ void * net_init (const char *pkglibdir, SANE_Status *status);
+ void * net_exit (void *self);
+ int net_get_sock (void *self);
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* !defined (net_obj_h_included) */
diff --git a/backend/profile.c b/backend/profile.c
new file mode 100644
index 0000000..57c0401
--- /dev/null
+++ b/backend/profile.c
@@ -0,0 +1,540 @@
+/* profile.c -- hardware colour correction coefficients
+ * Copyright (C) 2001--2009 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+/*! Hardware colour correction coefficients (CCC).
+
+ Each entry starts with a unique identifier, followed by four CCC
+ profiles; the first is for reflective materials, the second for
+ colour negatives, the third for monochrome negatives, and the
+ fourth and last one is for colour positives.
+*/
+const EpsonScanHardRec _epson_scan_hard[] = {
+ {0x00, /* default */
+ {{1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x05,
+ {{1.1419,-0.0596,-0.0825,-0.1234, 1.2812,-0.1413, 0.0703,-0.5720, 1.5016},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.1419,-0.0596,-0.0825,-0.1234, 1.2812,-0.1413, 0.0703,-0.5720, 1.5016}}},
+ {0x06,
+ {{1.1442,-0.0705,-0.0737,-0.0702, 1.1013,-0.0311,-0.0080,-0.3588, 1.3668},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x07,
+ {{1.1967,-0.1379,-0.0588,-0.0538, 1.0385, 0.0153, 0.0348,-0.4070, 1.3721},
+ {1.0010,-0.0010, 0.0000,-0.1120, 1.1710,-0.0590, 0.0000,-0.0910, 1.0920},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.1967,-0.1379,-0.0588,-0.0538, 1.0385, 0.0153, 0.0348,-0.4070, 1.3721}}},
+ {0x0D,
+ {{1.1980,-0.1365,-0.0616,-0.1530, 1.1729,-0.0198,-0.0025,-0.2776, 1.2801},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.1980,-0.1365,-0.0616,-0.1530, 1.1729,-0.0198,-0.0025,-0.2776, 1.2801}}},
+ {0x0F,
+ {{1.0961,-0.0181,-0.0779,-0.1279, 1.1957,-0.0678, 0.0315,-0.3891, 1.3576},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0961,-0.0181,-0.0779,-0.1279, 1.1957,-0.0678, 0.0315,-0.3891, 1.3576}}},
+ {0x15,
+ {{1.0999,-0.0425,-0.0574,-0.0806, 1.0835,-0.0028, 0.0057,-0.2924, 1.2866},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x16,
+ {{1.2020,-0.1518,-0.0502,-0.0847, 1.1385,-0.0538, 0.0059,-0.3255, 1.3196},
+ {1.0030,-0.0030, 0.0000,-0.0980, 1.1500,-0.0520,-0.0030,-0.0840, 1.0880},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.2456,-0.1617,-0.0839,-0.1160, 1.1862,-0.0702,-0.0036,-0.3438, 1.3473}}},
+ {0x18,
+ {{1.1339,-0.0526,-0.0813,-0.1177, 1.1661,-0.0485,-0.0030,-0.3298, 1.3328},
+ {1.0010,-0.0010, 0.0000,-0.1120, 1.1710,-0.0590, 0.0000,-0.0910, 1.0920},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.2066,-0.0360,-0.1706,-0.1313, 1.2523,-0.1210,-0.0299,-0.3377, 1.3676}}},
+ {0x1A,
+ {{1.0986, 0.0235,-0.1221,-0.1294, 1.0896, 0.0399, 0.0928,-0.6043, 1.5115},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x1B,
+ {{1.1855,-0.1372,-0.0483,-0.2060, 1.2468,-0.0407, 0.0358,-0.3059, 1.2701},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.1976,-0.1182,-0.0794,-0.1578, 1.2720,-0.1142, 0.0122,-0.3467, 1.3345}}},
+ {0x1D,
+ {{1.0675,-0.0586,-0.0088,-0.0332, 0.9716, 0.0616, 0.0175,-0.4054, 1.3879},
+ {1.0090,-0.0090, 0.0000,-0.0390, 1.0750,-0.0360,-0.0070,-0.1060, 1.1130},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.1394,-0.0829,-0.0564,-0.0003, 1.0008,-0.0004,-0.0059,-0.3674, 1.3733}}},
+ {0x1F,
+ {{1.0800,-0.0607,-0.0193,-0.0787, 1.0846,-0.0059, 0.0135,-0.3334, 1.3199},
+ {1.0040,-0.0040, 0.0000,-0.0780, 1.1360,-0.0570,-0.0020,-0.0810, 1.0830},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.1334,-0.0929,-0.0405,-0.0418, 1.0689,-0.0271,-0.0521,-0.3262, 1.3783}}},
+ {0x21,
+ {{1.0919,-0.0739,-0.0180,-0.0941, 1.1150,-0.0209, 0.0220,-0.3744, 1.3524},
+ {1.0090,-0.0100, 0.0010,-0.0720, 1.1310,-0.0600, 0.0000,-0.1000, 1.1000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.1374,-0.1396, 0.0021,-0.0489, 1.0655,-0.0166, 0.0081,-0.3492, 1.3411}}},
+ {0x23,
+ {{1.0339,-0.0166,-0.0173,-0.0117, 0.9797, 0.0319, 0.0010,-0.3609, 1.3599},
+ {1.0090,-0.0090, 0.0000,-0.0390, 1.0750,-0.0360,-0.0070,-0.1060, 1.1130},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.1666,-0.0898,-0.0768,-0.0076, 1.0157,-0.0081, 0.0012,-0.3048, 1.3036}}},
+ {0x25,
+ {{1.0800,-0.0607,-0.0193,-0.0787, 1.0846,-0.0059, 0.0135,-0.3334, 1.3199},
+ {1.0040,-0.0040, 0.0000,-0.0780, 1.1360,-0.0570,-0.0020,-0.0810, 1.0830},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.1334,-0.0929,-0.0405,-0.0418, 1.0689,-0.0271,-0.0521,-0.3262, 1.3783}}},
+ {0x27,
+ {{1.0919,-0.0739,-0.0180,-0.0941, 1.1150,-0.0209, 0.0220,-0.3744, 1.3524},
+ {1.0083,-0.0094, 0.0011,-0.0760, 1.1379,-0.0619,-0.0002,-0.0945, 1.0947},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.1952,-0.1519,-0.0433,-0.0932, 1.1613,-0.0681,-0.0418,-0.3140, 1.3558}}},
+ {0x29,
+ {{1.0369,-0.0210,-0.0160,-0.0820, 1.1160,-0.0341, 0.0150,-0.5035, 1.4885},
+ {1.0122,-0.0151, 0.0029,-0.0861, 1.1402,-0.0542,-0.0061,-0.1607, 1.1669},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.1764,-0.1749,-0.0014,-0.0590, 1.0983,-0.0393, 0.0208,-0.5194, 1.4986}}},
+ {0x2B,
+ {{1.0305,-0.0116,-0.0189,-0.0936, 1.1245,-0.0309,-0.0072,-0.1413, 1.1485},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x32,
+ {{1.0932,-0.0529,-0.0403,-0.1077, 1.1416,-0.0338, 0.0079,-0.5525, 1.5446},
+ {1.0259,-0.0356, 0.0097,-0.1085, 1.2225,-0.1140,-0.0046,-0.1848, 1.1894},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.2720,-0.2665,-0.0054,-0.0672, 1.1301,-0.0629,-0.0048,-0.3917, 1.3965}}},
+ {0x2D,
+ {{1.0436,-0.0078,-0.0359,-0.0169, 1.0114, 0.0056, 0.0308,-0.4425, 1.4117},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x3A,
+ {{1.1150,-0.0677,-0.0473,-0.1179, 1.1681,-0.0502, 0.0052,-0.4858, 1.4806},
+ {1.0133,-0.0151, 0.0017,-0.1216, 1.2207,-0.0991,-0.0003,-0.1512, 1.1515},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.2105,-0.1644,-0.0461,-0.1124, 1.1945,-0.0820,-0.0450,-0.3367, 1.3817}}},
+ {0x36,
+ {{1.0848,-0.0153,-0.0695,-0.0902, 1.0611, 0.0291, 0.0344,-0.5002, 1.4658},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x34,
+ {{1.1032,-0.0590,-0.0442,-0.1915, 1.3371,-0.1456, 0.0387,-0.5804, 1.5417},
+ {1.0232,-0.0258, 0.0026,-0.1296, 1.2882,-0.1587,-0.0011,-0.1928, 1.1940},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.2662,-0.2664, 0.0002,-0.1050, 1.3168,-0.2118,-0.0058,-0.4370, 1.4428}}},
+ {0x38,
+ {{1.1150,-0.0677,-0.0473,-0.1179, 1.1681,-0.0502, 0.0052,-0.4858, 1.4806},
+ {1.0133,-0.0151, 0.0017,-0.1216, 1.2207,-0.0991,-0.0003,-0.1512, 1.1515},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.2105,-0.1644,-0.0461,-0.1124, 1.1945,-0.0820,-0.0450,-0.3367, 1.3817}}},
+ {0x37,
+ {{0.9640, 0.1455,-0.1095, 0.0108, 1.1933,-0.2041, 0.0071,-0.3487, 1.3416},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x3F,
+ {{1.1223,-0.0985,-0.0238,-0.0847, 1.1502,-0.0655, 0.0118,-0.5022, 1.4904},
+ {1.0077,-0.0129, 0.0052,-0.0904, 1.1785,-0.0881, 0.0000,-0.1528, 1.1528},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.1927,-0.1646,-0.0280,-0.0655, 1.1033,-0.0378, 0.0034,-0.4173, 1.4139}}},
+ {0x41,
+ {{1.0732,-0.0581,-0.0150,-0.0897, 1.1553,-0.0657,-0.0179,-0.6500, 1.6679},
+ {1.0163,-0.0203, 0.0040,-0.1125, 1.1797,-0.0672,-0.0091,-0.2343, 1.2434},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.2437,-0.2022,-0.0415,-0.0352, 1.0735,-0.0383,-0.0188,-0.5020, 1.5209}}},
+ {0x43,
+ {{1.0782,-0.0697,-0.0085,-0.1605, 1.2862,-0.1257, 0.0148,-0.5854, 1.5706},
+ {1.0136,-0.0151, 0.0016,-0.1836, 1.3422,-0.1586,-0.0014,-0.1851, 1.1865},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.1491,-0.1456,-0.0035,-0.0990, 1.2657,-0.1666, 0.0015,-0.3868, 1.3853}}},
+ {0x46,
+ {{0.9828, 0.0924,-0.0752, 0.0255, 1.1510,-0.1765, 0.0049,-0.3250, 1.3201},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x48,
+ {{0.9716, 0.0927,-0.0643, 0.0010, 1.1068,-0.1078, 0.0101,-0.3046, 1.2945},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x49,
+ {{0.9640, 0.1455,-0.1095, 0.0108, 1.1933,-0.2041, 0.0071,-0.3487, 1.3416},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x4B,
+ {{1.1150,-0.0677,-0.0473,-0.1179, 1.1681,-0.0502, 0.0052,-0.4858, 1.4806},
+ {1.0133,-0.0151, 0.0017,-0.1216, 1.2207,-0.0991,-0.0003,-0.1512, 1.1515},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.2105,-0.1644,-0.0461,-0.1124, 1.1945,-0.0820,-0.0450,-0.3367, 1.3817}}},
+ {0x4D,
+ {{1.1011,-0.0824,-0.0186,-0.0970, 1.1991,-0.1021,-0.0161,-0.6247, 1.6408},
+ {1.0259,-0.0356, 0.0097,-0.1085, 1.2225,-0.1140,-0.0046,-0.1848, 1.1894},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.2150,-0.2074,-0.0076,-0.0521, 1.1430,-0.0909,-0.0204,-0.4156, 1.4360}}},
+ {0x4F,
+ {{1.1052,-0.0850,-0.0202,-0.1050, 1.2294,-0.1245,-0.0486,-0.4160, 1.4646},
+ {1.0255,-0.0272, 0.0017,-0.0919, 1.2098,-0.1180,-0.0021,-0.1296, 1.1317},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.2950,-0.2619,-0.0332,-0.0562, 1.1587,-0.1025,-0.0397,-0.3100, 1.3497}}},
+ {0x51,
+ {{1.0614,-0.0361,-0.0253,-0.1081, 1.1320,-0.0240,-0.0536,-0.2045, 1.2580},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x52,
+ {{1.0978,-0.0806,-0.0173,-0.0802, 1.1515,-0.0713,-0.0476,-0.4656, 1.5132},
+ {1.0192,-0.0192, 0.0000,-0.0974, 1.1846,-0.0872,-0.0031,-0.1797, 1.1828},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.2490,-0.2030,-0.0460,-0.0469, 1.1046,-0.0577,-0.0361,-0.3857, 1.4217}}},
+ {0x54,
+ {{1.0905,-0.0654,-0.0251,-0.1030, 1.1801,-0.0771,-0.0685,-0.4238, 1.4923},
+ {1.0206,-0.0207, 0.0000,-0.0890, 1.1770,-0.0880,-0.0014,-0.1450, 1.1464},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.3041,-0.2907,-0.0134,-0.0383, 1.0908,-0.0525,-0.0327,-0.2947, 1.3275}}},
+ {0x56,
+ {{1.0784,-0.0560,-0.0224,-0.1793, 1.2234,-0.0441,-0.0041,-0.2636, 1.2677},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x57,
+ {{0.9828, 0.0924,-0.0752, 0.0255, 1.1510,-0.1765, 0.0049,-0.3250, 1.3201},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x58,
+ {{0.9716, 0.0927,-0.0643, 0.0010, 1.1068,-0.1078, 0.0101,-0.3046, 1.2945},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x59,
+ {{0.9716, 0.0927,-0.0643, 0.0010, 1.1068,-0.1078, 0.0101,-0.3046, 1.2945},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x5B,
+ {{0.9764, 0.1095,-0.0859, 0.0149, 1.1154,-0.1303, 0.0051,-0.2851, 1.2800},
+ {1.0024,-0.0149, 0.0124,-0.2569, 1.3432,-0.0864,-0.0043,-0.1306, 1.1349},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.1003,-0.0493,-0.0510,-0.1607, 1.2748,-0.1142,-0.0059,-0.3161, 1.3220}}},
+ {0x5D,
+ {{0.9764, 0.1095,-0.0859, 0.0149, 1.1154,-0.1303, 0.0051,-0.2851, 1.2800},
+ {1.0024,-0.0149, 0.0124,-0.2569, 1.3432,-0.0864,-0.0043,-0.1306, 1.1349},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.1003,-0.0493,-0.0510,-0.1607, 1.2748,-0.1142,-0.0059,-0.3161, 1.3220}}},
+ {0x5F,
+ {{1.0697,-0.0561,-0.0137,-0.0824, 1.1291,-0.0467,-0.0390,-0.5218, 1.5608},
+ {1.0208,-0.0209, 0.0000,-0.0923, 1.2017,-0.1093,-0.0020,-0.1290, 1.1310},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.2606,-0.2125,-0.0482,-0.0567, 1.1441,-0.0874,-0.0431,-0.3490, 1.3921}}},
+ {0x61,
+ {{1.0921,-0.0722,-0.0199,-0.0831, 1.1550,-0.0718,-0.0452,-0.3721, 1.4173},
+ {1.0168,-0.0168, 0.0000,-0.0953, 1.1928,-0.0975,-0.0012,-0.1235, 1.1247},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.2603,-0.2763, 0.0155,-0.0398, 1.1033,-0.0635,-0.0249,-0.2675, 1.2924}}},
+ {0x63,
+ {{1.0976,-0.0789,-0.0187,-0.0958, 1.1821,-0.0863,-0.0565,-0.4179, 1.4744},
+ {1.0250,-0.0267, 0.0016,-0.0930, 1.2108,-0.1178,-0.0022,-0.1296, 1.1317},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.3111,-0.2979,-0.0132,-0.0441, 1.1148,-0.0707,-0.0348,-0.2971, 1.3319}}},
+ {0x65,
+ {{1.0359,-0.0146,-0.0213,-0.0752, 1.0963,-0.0211,-0.0456,-0.3238, 1.3693},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x66,
+ {{1.0878,-0.0667,-0.0211,-0.0892, 1.1513,-0.0622,-0.0654,-0.5175, 1.5829},
+ {1.0208,-0.0209, 0.0000,-0.0923, 1.2017,-0.1093,-0.0020,-0.1290, 1.1310},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.2688,-0.2522,-0.0166,-0.0559, 1.1291,-0.0733,-0.0377,-0.3519, 1.3896}}},
+ {0x68,
+ {{1.0950,-0.0646,-0.0305,-0.0792, 1.1398,-0.0606,-0.0123,-0.5175, 1.5298},
+ {1.0258,-0.0306, 0.0048,-0.0995, 1.2173,-0.1178,-0.0054,-0.1242, 1.1296},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.2697,-0.2501,-0.0195,-0.0351, 1.1236,-0.0885,-0.0131,-0.3268, 1.3400}}},
+ {0x6A,
+ {{0.9828, 0.0924,-0.0752, 0.0255, 1.1510,-0.1765, 0.0049,-0.3250, 1.3201},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x6B,
+ {{0.9828, 0.0924,-0.0752, 0.0255, 1.1510,-0.1765, 0.0049,-0.3250, 1.3201},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x6C,
+ {{0.9716, 0.0927,-0.0643, 0.0010, 1.1068,-0.1078, 0.0101,-0.3046, 1.2945},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x70,
+ {{0.9533, 0.0885,-0.0418, 0.0033, 1.0627,-0.0660,-0.0137,-0.1904, 1.2041},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x71,
+ {{1.0697,-0.0561,-0.0137,-0.0824, 1.1291,-0.0467,-0.0390,-0.5218, 1.5608},
+ {1.0208,-0.0209, 0.0000,-0.0923, 1.2017,-0.1093,-0.0020,-0.1290, 1.1310},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.2606,-0.2125,-0.0482,-0.0567, 1.1441,-0.0874,-0.0431,-0.3490, 1.3921}}},
+ {0x73,
+ {{1.0828,-0.0739,-0.0089,-0.0895, 1.1597,-0.0702,-0.0531,-0.4291, 1.4822},
+ {1.0258,-0.0306, 0.0048,-0.0995, 1.2173,-0.1178,-0.0054,-0.1242, 1.1296},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.2579,-0.2384,-0.0195,-0.0569, 1.1454,-0.0884,-0.0411,-0.3072, 1.3483}}},
+ {0x75,
+ {{1.0828,-0.0739,-0.0089,-0.0895, 1.1597,-0.0702,-0.0531,-0.4291, 1.4822},
+ {1.0258,-0.0306, 0.0048,-0.0995, 1.2173,-0.1178,-0.0054,-0.1242, 1.1296},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.2579,-0.2384,-0.0195,-0.0569, 1.1454,-0.0884,-0.0411,-0.3072, 1.3483}}},
+ {0x77,
+ {{0.9716, 0.0927,-0.0643, 0.0010, 1.1068,-0.1078, 0.0101,-0.3046, 1.2945},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x78,
+ {{1.0784,-0.0560,-0.0224,-0.1793, 1.2234,-0.0441,-0.0041,-0.2636, 1.2677},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x79,
+ {{1.0614,-0.0361,-0.0253,-0.1081, 1.1320,-0.0240,-0.0536,-0.2045, 1.2580},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x7A,
+ {{1.1754,-0.1173,-0.0580,-0.0687, 1.1307,-0.0620,-0.0255,-0.4699, 1.4954},
+ {1.0150,-0.0173, 0.0022,-0.0853, 1.2238,-0.1384,-0.0073,-0.1490, 1.1562},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.4283,-0.4335, 0.0052,-0.0170, 1.1308,-0.1138,-0.0147,-0.2230, 1.2377}}},
+ {0x7C,
+ {{1.2470,-0.2041,-0.0429,-0.1920, 1.2918,-0.0998,-0.0100,-0.2503, 1.2603},
+ {1.0050,-0.0076, 0.0026,-0.2532, 1.1289, 0.1243,-0.0733,-0.0960, 1.1693},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.4724,-0.4599,-0.0125,-0.0876, 1.1562,-0.0686,-0.0097,-0.2278, 1.2375}}},
+ {0x7E,
+ {{0.9828, 0.0924,-0.0752, 0.0255, 1.1510,-0.1765, 0.0049,-0.3250, 1.3201},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x7F,
+ {{1.0936,-0.0142,-0.0795,-0.0001, 1.0951,-0.0949, 0.0308,-0.2967, 1.2659},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x80,
+ {{1.0936,-0.0142,-0.0795,-0.0001, 1.0951,-0.0949, 0.0308,-0.2967, 1.2659},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x81,
+ {{1.1090,-0.0304,-0.0786, 0.0194, 1.1078,-0.1272,-0.0077,-0.1293, 1.1370},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x82,
+ {{1.1622,-0.1102,-0.0519,-0.0717, 1.1060,-0.0343,-0.0248,-0.4138, 1.4385},
+ {0.9913, 0.0082, 0.0005,-0.1259, 1.0452, 0.0807,-0.0072,-0.0767, 1.0839},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.3900,-0.3008,-0.0892,-0.0254, 1.0890,-0.0636,-0.0300,-0.2501, 1.2801}}},
+ {0x84,
+ {{1.0934,-0.0042,-0.0892, 0.0052, 1.1019,-0.1071, 0.0259,-0.2651, 1.2392},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x85,
+ {{1.0534, 0.0399,-0.0934, 0.0098, 1.0589,-0.0687, 0.0016,-0.1131, 1.1115},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x86,
+ {{1.1945,-0.1413,-0.0532,-0.1929, 1.2525,-0.0596,-0.0235,-0.2761, 1.2996},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x87,
+ {{1.1978,-0.1417,-0.0561,-0.0852, 1.1610,-0.0758,-0.0395,-0.3212, 1.3607},
+ {1.0000, 0.0009,-0.0009,-0.1268, 1.0523, 0.0745,-0.0075,-0.0873, 1.0948},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.4475,-0.3957,-0.0518,-0.0138, 1.0644,-0.0506,-0.0199,-0.2050, 1.2249}}},
+ {0x97,
+ {{1.1115,-0.0377,-0.0738,-0.0658, 1.0624, 0.0034, 0.0042,-0.2883, 1.2841},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x89,
+ {{1.1115,-0.0377,-0.0738,-0.0658, 1.0624, 0.0034, 0.0042,-0.2883, 1.2841},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x8A,
+ {{1.1221,-0.0396,-0.0825,-0.0718, 1.0822,-0.0104, 0.0112,-0.2995, 1.2883},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x8B,
+ {{1.2402,-0.1891,-0.0511,-0.1535, 1.2008,-0.0473,-0.0316,-0.3293, 1.3609},
+ {1.0027,-0.0048, 0.0021,-0.2067, 1.0878, 0.1189,-0.0408,-0.0767, 1.1175},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.4524,-0.4346,-0.0178,-0.0601, 1.1273,-0.0672,-0.0173,-0.1823, 1.1996}}},
+ {0x8D,
+ {{1.0936,-0.0142,-0.0795,-0.0001, 1.0951,-0.0949, 0.0308,-0.2967, 1.2659},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x8E,
+ {{1.0936,-0.0142,-0.0795,-0.0001, 1.0951,-0.0949, 0.0308,-0.2967, 1.2659},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x8F,
+ {{1.1090,-0.0304,-0.0786, 0.0194, 1.1078,-0.1272,-0.0077,-0.1293, 1.1370},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x90,
+ {{1.0316, 0.0864,-0.1180, 0.0268, 1.1111,-0.1379, 0.0213,-0.2235, 1.2022},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x91,
+ {{1.0777, 0.0152,-0.0929, 0.0244, 1.1221,-0.1465, 0.0103,-0.1544, 1.1441},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x92,
+ {{1.0777, 0.0152,-0.0929, 0.0244, 1.1221,-0.1465, 0.0103,-0.1544, 1.1441},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x93,
+ {{1.0934,-0.0042,-0.0892, 0.0052, 1.1019,-0.1071, 0.0259,-0.2651, 1.2392},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x96,
+ {{1.1090,-0.0304,-0.0786, 0.0194, 1.1078,-0.1272,-0.0077,-0.1293, 1.1370},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x98,
+ {{1.0936,-0.0142,-0.0795,-0.0001, 1.0951,-0.0949, 0.0308,-0.2967, 1.2659},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x99,
+ {{1.1090,-0.0304,-0.0786, 0.0194, 1.1078,-0.1272,-0.0077,-0.1293, 1.1370},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x9A,
+ {{1.0779, 0.0132,-0.0911, 0.0214, 1.1003,-0.1217, 0.0109,-0.1487, 1.1378},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x9B,
+ {{1.0779, 0.0132,-0.0911, 0.0214, 1.1003,-0.1217, 0.0109,-0.1487, 1.1378},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x9C,
+ {{1.0316, 0.0864,-0.1180, 0.0268, 1.1111,-0.1379, 0.0213,-0.2235, 1.2022},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x9D,
+ {{1.0316, 0.0864,-0.1180, 0.0268, 1.1111,-0.1379, 0.0213,-0.2235, 1.2022},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x9E,
+ {{1.0534, 0.0399,-0.0934, 0.0098, 1.0589,-0.0687, 0.0016,-0.1131, 1.1115},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0x9F,
+ {{1.0777, 0.0152,-0.0929, 0.0244, 1.1221,-0.1465, 0.0103,-0.1544, 1.1441},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0xA0,
+ {{1.0777, 0.0152,-0.0929, 0.0244, 1.1221,-0.1465, 0.0103,-0.1544, 1.1441},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000}}},
+ {0xA1,
+ {{1.2578,-0.2140,-0.0438,-0.1939, 1.2856,-0.0917,-0.0258,-0.2642, 1.2900},
+ {0.9989,-0.0018, 0.0029,-0.2608, 1.1305, 0.1303,-0.0802,-0.0807, 1.1609},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.4431,-0.4193,-0.0238,-0.0915, 1.1507,-0.0592,-0.0226,-0.1978, 1.2204}}},
+};
+
+const EpsonScanHardRec *epson_scan_hard = _epson_scan_hard;
diff --git a/backend/tests/45532d48333030.xml b/backend/tests/45532d48333030.xml
new file mode 100644
index 0000000..e7d6a04
--- /dev/null
+++ b/backend/tests/45532d48333030.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<scanner_entry>
+
+ <interfaces>
+ <usb vendor="04b8" product="012b"/>
+ <net status="unsupported" note="option"/>
+ </interfaces>
+
+ <device>
+ <firmware name="ES-H300"/>
+ <model name="GT-2500"/>
+ <model name="ES-H300" region="japan"/>
+ </device>
+
+ <profile-set>
+ <profile type="reflective">
+ <rr value=" 1.0359"/><rg value="-0.0146"/><rb value="-0.0213"/>
+ <gr value="-0.0752"/><gg value=" 1.0963"/><gb value="-0.0211"/>
+ <br value="-0.0456"/><bg value="-0.3238"/><bb value=" 1.3693"/>
+ </profile>
+ </profile-set>
+
+ <command-set type="extended" level="B8">
+ <command name="set_focus_position" code="1B70" status="disabled"/>
+ <command name="feed" code="19" status="disabled"/>
+ </command-set>
+
+</scanner_entry>
diff --git a/backend/tests/47542d58393730.xml b/backend/tests/47542d58393730.xml
new file mode 100644
index 0000000..26cb7e7
--- /dev/null
+++ b/backend/tests/47542d58393730.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<scanner_entry>
+
+ <interfaces>
+ <usb vendor="04b8" product="012b"/>
+ <net status="unsupported" note="option"/>
+ </interfaces>
+
+ <profile-set>
+ <profile type="reflective">
+ <rr value=" 1.1978"/><rg value="-0.1417"/><rb value="-0.0561"/>
+ <gr value="-0.0852"/><gg value=" 1.161"/><gb value="-0.0758"/>
+ <br value="-0.0395"/><bg value="-0.3212"/><bb value=" 1.3607"/>
+ </profile>
+ <profile type="color negative">
+ <rr value=" 1.000"/><rg value="0.0009"/><rb value="-0.0009"/>
+ <gr value="-0.1268"/><gg value=" 1.0523"/><gb value="0.0745"/>
+ <br value="-0.0075"/><bg value="-0.0873"/><bb value=" 1.0948"/>
+ </profile>
+ <profile type="positive">
+ <rr value=" 1.4475"/><rg value="-0.3957"/><rb value="-0.0518"/>
+ <gr value="-0.0138"/><gg value=" 1.0644"/><gb value="-0.0506"/>
+ <br value="-0.0199"/><bg value="-0.205"/><bb value=" 1.2249"/>
+ </profile>
+ </profile-set>
+
+ <device>
+ <firmware name="GT-X970"/>
+ </device>
+
+ <command-set type="extended" level="B8">
+ <command name="set_focus_position" code="1B70" status="disabled"/>
+ <command name="feed" code="19" status="disabled"/>
+ <command name="lock"/>
+ <command name="unlock"/>
+ </command-set>
+
+</scanner_entry>
diff --git a/backend/tests/50657266656374696f6e363130.xml b/backend/tests/50657266656374696f6e363130.xml
new file mode 100644
index 0000000..2ac6c46
--- /dev/null
+++ b/backend/tests/50657266656374696f6e363130.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<scanner_entry>
+
+ <interfaces>
+ <usb vendor="04b8" product="012b"/>
+ <net status="unsupported" note="option"/>
+ </interfaces>
+
+ <device>
+ <firmware name="Perfection610"/>
+ <model name="Perfection 610"/>
+ </device>
+
+ <profile-set>
+ <profile type="reflective">
+ <rr value=" 1.1442"/><rg value="-0.0705"/><rb value="-0.0737"/>
+ <gr value="-0.0702"/><gg value=" 1.1013"/><gb value="-0.0311"/>
+ <br value="-0.0080"/><bg value="-0.3588"/><bb value=" 1.3668"/>
+ </profile>
+ </profile-set>
+
+ <command-set type="extended" level="B8">
+ <command name="set_focus_position" code="1B70" status="disabled"/>
+ <command name="feed" code="19" status="disabled"/>
+ </command-set>
+
+</scanner_entry>
diff --git a/backend/tests/Makefile.am b/backend/tests/Makefile.am
new file mode 100644
index 0000000..ef275d1
--- /dev/null
+++ b/backend/tests/Makefile.am
@@ -0,0 +1,79 @@
+## Makefile.am -- an -*- automake -*- template for Makefile.in
+## Copyright (C) 2008 SEIKO EPSON CORPORATION
+##
+## License: GPLv2+
+## Authors: AVASYS CORPORATION
+##
+## This file is part of the "Image Scan!" build infra-structure.
+##
+## The "Image Scan!" build infra-structure is free software.
+## You can redistribute it and/or modify it under the terms of the GNU
+## General Public License as published by the Free Software Foundation;
+## either version 2 of the License or at your option any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You ought to have received a copy of the GNU General Public License
+## along with this package. If not, see <http://www.gnu.org/licenses/>.
+
+
+AM_CPPFLAGS = $(XML_CFLAGS)
+
+check_PROGRAMS = \
+ xmltest
+
+TESTS =
+
+xmltest_LDADD = ../libepkowa.la
+xmltest_SOURCES = xmltest.c xmltest.h
+
+EXTRA_DIST = \
+ 47542d58393730.xml \
+ 45532d48333030.xml \
+ 50657266656374696f6e363130.xml \
+ xmltest-runner.sh
+
+if HAVE_CXXTESTGEN
+
+check_PROGRAMS += \
+ cfg-obj \
+ net-obj \
+ network \
+ model-info
+
+TESTS += \
+ cfg-obj \
+ net-obj \
+ model-info
+
+cfg_obj_LDADD = ../libepkowa.la
+cfg_obj_SOURCES = \
+ test-cfg-obj.cc \
+ test-cfg-obj.hh
+
+net_obj_LDADD = ../libepkowa.la
+net_obj_SOURCES = \
+ test-net-obj.cc \
+ test-net-obj.hh
+
+model_info_LDADD = ../libepkowa.la
+model_info_SOURCES = \
+ test-model-info.cc \
+ test-model-info.hh
+
+# Use the CxxTest code generator on all files matching test-*.hh to
+# create the corresponding test-*.cc file. This convention is used
+# so that we can still use regular source code without this getting
+# clobbered as soon as we change the header file.
+
+.hh.cc:
+ @if test xtest- = "x`echo $^ | sed -n 's|^\(test-\).*|\1|p'`"; \
+ then \
+ echo "$(CXXTESTGEN) $(CXXTESTGEN_OPTS) -o $@ $^"; \
+ $(CXXTESTGEN) $(CXXTESTGEN_OPTS) -o $@ $^; \
+ fi
+
+endif ## HAVE_CXXTESTGEN
diff --git a/backend/tests/network.c b/backend/tests/network.c
new file mode 100644
index 0000000..8dd6dec
--- /dev/null
+++ b/backend/tests/network.c
@@ -0,0 +1,86 @@
+/* network.c -- a network mock program for use by unit tests
+ * Copyright (C) 2008 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of Image Scan!'s SANE backend test suite.
+ *
+ * Image Scan!'s SANE backend test suite is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+int
+main (int argc, char *argv[])
+{
+ socklen_t n;
+ int r;
+ int s;
+ int as;
+ struct sockaddr_in addr;
+
+ s = socket (AF_INET, SOCK_STREAM, 0);
+ if (0 > s)
+ {
+ perror ("socket");
+ return EXIT_FAILURE;
+ }
+
+ memset (&addr, 0, sizeof (addr));
+ addr.sin_family = PF_INET;
+ addr.sin_port = 0;
+ addr.sin_addr.s_addr = INADDR_ANY;
+
+ r = bind (s, (struct sockaddr *) &addr, sizeof (addr));
+ if (0 > r)
+ {
+ perror ("bind");
+ return EXIT_FAILURE;
+ }
+
+ n = sizeof (addr);
+ r = getsockname (s, (struct sockaddr *) &addr, &n);
+ if (0 > r)
+ {
+ perror ("getsockname");
+ return EXIT_FAILURE;
+ }
+
+ setvbuf (stdout, NULL, _IONBF, BUFSIZ);
+ fprintf (stdout, "%d\n", ntohs (addr.sin_port));
+ fclose (stdout); /* not strictly needed */
+
+ listen (s, 0);
+ as = accept (s, (struct sockaddr*) &addr, (socklen_t*) &n);
+
+ pause ();
+
+ close (s);
+ close (as);
+
+ return EXIT_SUCCESS;
+}
diff --git a/backend/tests/test-cfg-obj.cc b/backend/tests/test-cfg-obj.cc
new file mode 100644
index 0000000..9fe213f
--- /dev/null
+++ b/backend/tests/test-cfg-obj.cc
@@ -0,0 +1,60 @@
+/* Generated file, do not edit */
+
+#ifndef CXXTEST_RUNNING
+#define CXXTEST_RUNNING
+#endif
+
+#define _CXXTEST_HAVE_STD
+#include <cxxtest/TestListener.h>
+#include <cxxtest/TestTracker.h>
+#include <cxxtest/TestRunner.h>
+#include <cxxtest/RealDescriptions.h>
+#include <cxxtest/ErrorPrinter.h>
+
+int main() {
+ return CxxTest::ErrorPrinter().run();
+}
+#include "test-cfg-obj.hh"
+
+static test_cfg_obj suite_test_cfg_obj;
+
+static CxxTest::List Tests_test_cfg_obj = { 0, 0 };
+CxxTest::StaticSuiteDescription suiteDescription_test_cfg_obj( "test-cfg-obj.hh", 89, "test_cfg_obj", suite_test_cfg_obj, Tests_test_cfg_obj );
+
+static class TestDescription_test_cfg_obj_test_life_cycle : public CxxTest::RealTestDescription {
+public:
+ TestDescription_test_cfg_obj_test_life_cycle() : CxxTest::RealTestDescription( Tests_test_cfg_obj, suiteDescription_test_cfg_obj, 93, "test_life_cycle" ) {}
+ void runTest() { suite_test_cfg_obj.test_life_cycle(); }
+} testDescription_test_cfg_obj_test_life_cycle;
+
+static class TestDescription_test_cfg_obj_test_life_cycle_status : public CxxTest::RealTestDescription {
+public:
+ TestDescription_test_cfg_obj_test_life_cycle_status() : CxxTest::RealTestDescription( Tests_test_cfg_obj, suiteDescription_test_cfg_obj, 102, "test_life_cycle_status" ) {}
+ void runTest() { suite_test_cfg_obj.test_life_cycle_status(); }
+} testDescription_test_cfg_obj_test_life_cycle_status;
+
+static class TestDescription_test_cfg_obj_test_key_query : public CxxTest::RealTestDescription {
+public:
+ TestDescription_test_cfg_obj_test_key_query() : CxxTest::RealTestDescription( Tests_test_cfg_obj, suiteDescription_test_cfg_obj, 115, "test_key_query" ) {}
+ void runTest() { suite_test_cfg_obj.test_key_query(); }
+} testDescription_test_cfg_obj_test_key_query;
+
+static class TestDescription_test_cfg_obj_test_option_value_query : public CxxTest::RealTestDescription {
+public:
+ TestDescription_test_cfg_obj_test_option_value_query() : CxxTest::RealTestDescription( Tests_test_cfg_obj, suiteDescription_test_cfg_obj, 123, "test_option_value_query" ) {}
+ void runTest() { suite_test_cfg_obj.test_option_value_query(); }
+} testDescription_test_cfg_obj_test_option_value_query;
+
+static class TestDescription_test_cfg_obj_test_key_mutator : public CxxTest::RealTestDescription {
+public:
+ TestDescription_test_cfg_obj_test_key_mutator() : CxxTest::RealTestDescription( Tests_test_cfg_obj, suiteDescription_test_cfg_obj, 134, "test_key_mutator" ) {}
+ void runTest() { suite_test_cfg_obj.test_key_mutator(); }
+} testDescription_test_cfg_obj_test_key_mutator;
+
+static class TestDescription_test_cfg_obj_test_net_registration : public CxxTest::RealTestDescription {
+public:
+ TestDescription_test_cfg_obj_test_net_registration() : CxxTest::RealTestDescription( Tests_test_cfg_obj, suiteDescription_test_cfg_obj, 146, "test_net_registration" ) {}
+ void runTest() { suite_test_cfg_obj.test_net_registration(); }
+} testDescription_test_cfg_obj_test_net_registration;
+
+#include <cxxtest/Root.cpp>
diff --git a/backend/tests/test-cfg-obj.hh b/backend/tests/test-cfg-obj.hh
new file mode 100644
index 0000000..ab8b851
--- /dev/null
+++ b/backend/tests/test-cfg-obj.hh
@@ -0,0 +1,254 @@
+// test-cfg-obj.hh -- test suite for configuration objects
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// License: GPLv2+|iscan
+// Authors: AVASYS CORPORATION
+//
+// This file is part of Image Scan!'s SANE backend test suite.
+//
+// Image Scan!'s SANE backend test suite is free software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You ought to have received a copy of the GNU General Public License
+// along with this package. If not, see <http://www.gnu.org/licenses/>.
+//
+//
+// Linking Image Scan!'s SANE backend statically or dynamically with
+// other modules is making a combined work based on this SANE backend.
+// Thus, the terms and conditions of the GNU General Public License
+// cover the whole combination.
+//
+// As a special exception, the copyright holders of Image Scan!'s SANE
+// backend give you permission to link Image Scan!'s SANE backend with
+// SANE frontends that communicate with Image Scan!'s SANE backend
+// solely through the SANE Application Programming Interface,
+// regardless of the license terms of these SANE frontends, and to
+// copy and distribute the resulting combined work under terms of your
+// choice, provided that every copy of the combined work is
+// accompanied by a complete copy of the source code of Image Scan!'s
+// SANE backend (the version of Image Scan!'s SANE backend used to
+// produce the combined work), being distributed under the terms of
+// the GNU General Public License plus this exception. An independent
+// module is a module which is not derived from or based on Image
+// Scan!'s SANE backend.
+//
+// As a special exception, the copyright holders of Image Scan!'s SANE
+// backend give you permission to link Image Scan!'s SANE backend with
+// independent modules that communicate with Image Scan!'s SANE
+// backend solely through the "Interpreter" interface, regardless of
+// the license terms of these independent modules, and to copy and
+// distribute the resulting combined work under terms of your choice,
+// provided that every copy of the combined work is accompanied by a
+// complete copy of the source code of Image Scan!'s SANE backend (the
+// version of Image Scan!'s SANE backend used to produce the combined
+// work), being distributed under the terms of the GNU General Public
+// License plus this exception. An independent module is a module
+// which is not derived from or based on Image Scan!'s SANE backend.
+//
+// Note that people who make modified versions of Image Scan!'s SANE
+// backend are not obligated to grant special exceptions for their
+// modified versions; it is their choice whether to do so. The GNU
+// General Public License gives permission to release a modified
+// version without this exception; this exception also makes it
+// possible to release a modified version which carries forward this
+// exception.
+
+
+#ifndef included_test_cfg_obj_hh
+#define included_test_cfg_obj_hh
+
+#ifndef __cplusplus
+#error "This is a C++ include file. Use a C++ compiler to compile"
+#error "code that includes this file."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "../cfg-obj.h"
+#include <cxxtest/TestSuite.h>
+
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <string>
+#include <unistd.h>
+
+#include "../message.h"
+
+
+class test_cfg_obj : public CxxTest::TestSuite
+{
+public:
+
+ void test_life_cycle (void)
+ {
+ cfg = cfg_init (dir.c_str (), NULL);
+ TS_ASSERT (cfg);
+
+ cfg = cfg_exit (cfg);
+ TS_ASSERT (!cfg);
+ }
+
+ void test_life_cycle_status (void)
+ {
+ SANE_Status status = SANE_STATUS_NO_MEM;
+
+ cfg = cfg_init (dir.c_str (), &status);
+ TS_ASSERT (cfg);
+
+ TS_ASSERT (SANE_STATUS_NO_MEM != status);
+
+ cfg = cfg_exit (cfg);
+ TS_ASSERT (!cfg);
+ }
+
+ void test_key_query (void)
+ {
+ cfg = cfg_init (dir.c_str (), NULL);
+ TS_ASSERT (cfg);
+
+ TS_ASSERT (cfg_has (cfg, CFG_KEY_USB));
+ }
+
+ void test_option_value_query (void)
+ {
+ cfg = cfg_init (dir.c_str (), NULL);
+ TS_ASSERT (cfg);
+
+ TS_ASSERT (cfg_has (cfg, CFG_KEY_OPTION));
+ TS_ASSERT (cfg_has_value (cfg, CFG_KEY_OPTION, "prefer-adf"));
+ TS_ASSERT (!cfg_has_value (cfg, CFG_KEY_OPTION, "not-a-valid-option"));
+ TS_ASSERT (!cfg_has_value (cfg, CFG_KEY_OPTION, "not-in-config"));
+ }
+
+ void test_key_mutator (void)
+ {
+ cfg = cfg_init (dir.c_str (), NULL);
+ TS_ASSERT (cfg);
+
+ TS_ASSERT (cfg_has (cfg, CFG_KEY_USB));
+ cfg_set (cfg, CFG_KEY_USB, false);
+ TS_ASSERT (!cfg_has (cfg, CFG_KEY_USB));
+ cfg_set (cfg, CFG_KEY_USB, true);
+ TS_ASSERT (cfg_has (cfg, CFG_KEY_USB));
+ }
+
+ void test_net_registration (void)
+ {
+ add_cfg_entry ("net\n");
+
+ cfg = cfg_init (dir.c_str (), NULL);
+ TS_ASSERT (!cfg_has (cfg, CFG_KEY_NET));
+ }
+
+private:
+
+ void *cfg;
+ std::string dir;
+
+ /*! Create a temporary, minimal configuration directory.
+
+ The configuration directory is made available to the unit test
+ environment via the \c SANE_CONFIG_DIR environment variable so
+ configuration objects (should) take note. In addition, a very
+ minimal configuration file, containing the \c usb keyword and
+ a few options is created in this directory as well.
+ */
+ void setUp (void)
+ {
+ int result;
+ msg_init ();
+
+ char dirname_template[] = ".cfg-obj-XXXXXX";
+ char *dirname = mkdtemp (dirname_template);
+ if (!dirname)
+ {
+ err_fatal ("mkdtemp: %s", strerror (errno));
+ }
+ require (dirname);
+
+ result = chdir (dirname);
+ require (0 == result);
+
+ std::ofstream ofs (cfg_file_name);
+ ofs << "usb\n";
+ ofs << "option prefer-adf \n";
+ ofs << "option not-a-valid-option\n";
+
+ result = chdir ("..");
+ require (0 == result);
+
+ result = setenv ("SANE_CONFIG_DIR", dirname, true);
+ require (0 == result);
+
+ cfg = NULL;
+ dir = dirname;
+ }
+
+ /*! Attempts to undo the effects of setUp().
+
+ The environment's \c SANE_CONFIG_DIR is unset and the temporary
+ configuration directory is recursively removed. Failures to
+ undo any of the necessary actions are logged but will not cause
+ a test to fail.
+ */
+ void tearDown (void)
+ {
+ int result;
+
+ if (0 != unsetenv ("SANE_CONFIG_DIR"))
+ {
+ err_minor ("unsetenv: %s", strerror (errno));
+ }
+
+ if (0 != chdir (dir.c_str ()))
+ {
+ err_minor ("%s: %s", dir.c_str (), strerror (errno));
+ }
+ else
+ {
+ if (0 != unlink (cfg_file_name))
+ {
+ err_minor ("%s: %s", cfg_file_name, strerror (errno));
+ }
+ if (0 != unlink ("usb"))
+ {
+ err_minor ("%s: %s", "usb", strerror (errno));
+ }
+ result = chdir ("..");
+ require (0 == result);
+ }
+
+ if (0 != rmdir (dir.c_str ()))
+ {
+ err_minor ("%s: %s", dir.c_str (), strerror (errno));
+ }
+ dir = "";
+
+ cfg = cfg_exit (cfg);
+ promise (!cfg);
+ }
+
+ void add_cfg_entry (const char *str)
+ {
+ chdir (dir.c_str ());
+ std::ofstream ofs (cfg_file_name,
+ std::ios_base::out | std::ios_base::app);
+
+ ofs << str;
+ chdir ("..");
+ }
+};
+
+
+#endif /* !defined (included_test_cfg_obj_hh) */
diff --git a/backend/tests/test-model-info.cc b/backend/tests/test-model-info.cc
new file mode 100644
index 0000000..bb5eb7e
--- /dev/null
+++ b/backend/tests/test-model-info.cc
@@ -0,0 +1,83 @@
+/* Generated file, do not edit */
+
+#ifndef CXXTEST_RUNNING
+#define CXXTEST_RUNNING
+#endif
+
+#define _CXXTEST_HAVE_STD
+#include <cxxtest/TestListener.h>
+#include <cxxtest/TestTracker.h>
+#include <cxxtest/TestRunner.h>
+#include <cxxtest/RealDescriptions.h>
+#include <cxxtest/ErrorPrinter.h>
+
+int main() {
+ return CxxTest::ErrorPrinter().run();
+}
+#include "test-model-info.hh"
+
+static test_model_cache_info suite_test_model_cache_info;
+
+static CxxTest::List Tests_test_model_cache_info = { 0, 0 };
+CxxTest::StaticSuiteDescription suiteDescription_test_model_cache_info( "test-model-info.hh", 111, "test_model_cache_info", suite_test_model_cache_info, Tests_test_model_cache_info );
+
+static class TestDescription_test_model_cache_info_test_cache_life_cycle : public CxxTest::RealTestDescription {
+public:
+ TestDescription_test_model_cache_info_test_cache_life_cycle() : CxxTest::RealTestDescription( Tests_test_model_cache_info, suiteDescription_test_model_cache_info, 115, "test_cache_life_cycle" ) {}
+ void runTest() { suite_test_model_cache_info.test_cache_life_cycle(); }
+} testDescription_test_model_cache_info_test_cache_life_cycle;
+
+static class TestDescription_test_model_cache_info_test_cache_life_cycle_status : public CxxTest::RealTestDescription {
+public:
+ TestDescription_test_model_cache_info_test_cache_life_cycle_status() : CxxTest::RealTestDescription( Tests_test_model_cache_info, suiteDescription_test_model_cache_info, 124, "test_cache_life_cycle_status" ) {}
+ void runTest() { suite_test_model_cache_info.test_cache_life_cycle_status(); }
+} testDescription_test_model_cache_info_test_cache_life_cycle_status;
+
+static class TestDescription_test_model_cache_info_test_cache_unique_entries : public CxxTest::RealTestDescription {
+public:
+ TestDescription_test_model_cache_info_test_cache_unique_entries() : CxxTest::RealTestDescription( Tests_test_model_cache_info, suiteDescription_test_model_cache_info, 137, "test_cache_unique_entries" ) {}
+ void runTest() { suite_test_model_cache_info.test_cache_unique_entries(); }
+} testDescription_test_model_cache_info_test_cache_unique_entries;
+
+static test_model_info suite_test_model_info;
+
+static CxxTest::List Tests_test_model_info = { 0, 0 };
+CxxTest::StaticSuiteDescription suiteDescription_test_model_info( "test-model-info.hh", 154, "test_model_info", suite_test_model_info, Tests_test_model_info );
+
+static class TestDescription_test_model_info_test_get_non_existent_model : public CxxTest::RealTestDescription {
+public:
+ TestDescription_test_model_info_test_get_non_existent_model() : CxxTest::RealTestDescription( Tests_test_model_info, suiteDescription_test_model_info, 158, "test_get_non_existent_model" ) {}
+ void runTest() { suite_test_model_info.test_get_non_existent_model(); }
+} testDescription_test_model_info_test_get_non_existent_model;
+
+static class TestDescription_test_model_info_test_get_non_existent_info : public CxxTest::RealTestDescription {
+public:
+ TestDescription_test_model_info_test_get_non_existent_info() : CxxTest::RealTestDescription( Tests_test_model_info, suiteDescription_test_model_info, 165, "test_get_non_existent_info" ) {}
+ void runTest() { suite_test_model_info.test_get_non_existent_info(); }
+} testDescription_test_model_info_test_get_non_existent_info;
+
+static class TestDescription_test_model_info_test_get_info_from_loaded_cache : public CxxTest::RealTestDescription {
+public:
+ TestDescription_test_model_info_test_get_info_from_loaded_cache() : CxxTest::RealTestDescription( Tests_test_model_info, suiteDescription_test_model_info, 171, "test_get_info_from_loaded_cache" ) {}
+ void runTest() { suite_test_model_info.test_get_info_from_loaded_cache(); }
+} testDescription_test_model_info_test_get_info_from_loaded_cache;
+
+static class TestDescription_test_model_info_test_get_existing_model : public CxxTest::RealTestDescription {
+public:
+ TestDescription_test_model_info_test_get_existing_model() : CxxTest::RealTestDescription( Tests_test_model_info, suiteDescription_test_model_info, 186, "test_get_existing_model" ) {}
+ void runTest() { suite_test_model_info.test_get_existing_model(); }
+} testDescription_test_model_info_test_get_existing_model;
+
+static class TestDescription_test_model_info_test_default_values : public CxxTest::RealTestDescription {
+public:
+ TestDescription_test_model_info_test_default_values() : CxxTest::RealTestDescription( Tests_test_model_info, suiteDescription_test_model_info, 195, "test_default_values" ) {}
+ void runTest() { suite_test_model_info.test_default_values(); }
+} testDescription_test_model_info_test_default_values;
+
+static class TestDescription_test_model_info_test_profile_equality_values : public CxxTest::RealTestDescription {
+public:
+ TestDescription_test_model_info_test_profile_equality_values() : CxxTest::RealTestDescription( Tests_test_model_info, suiteDescription_test_model_info, 207, "test_profile_equality_values" ) {}
+ void runTest() { suite_test_model_info.test_profile_equality_values(); }
+} testDescription_test_model_info_test_profile_equality_values;
+
+#include <cxxtest/Root.cpp>
diff --git a/backend/tests/test-model-info.hh b/backend/tests/test-model-info.hh
new file mode 100644
index 0000000..1796b03
--- /dev/null
+++ b/backend/tests/test-model-info.hh
@@ -0,0 +1,241 @@
+// test-model-info.hh -- test suite for model info objects
+// Copyright (C) 2010 SEIKO EPSON CORPORATION
+//
+// License: GPLv2+|iscan
+// Authors: AVASYS CORPORATION
+//
+// This file is part of Image Scan!'s SANE backend test suite.
+//
+// Image Scan!'s SANE backend test suite is free software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You ought to have received a copy of the GNU General Public License
+// along with this package. If not, see <http://www.gnu.org/licenses/>.
+//
+//
+// Linking Image Scan!'s SANE backend statically or dynamically with
+// other modules is making a combined work based on this SANE backend.
+// Thus, the terms and conditions of the GNU General Public License
+// cover the whole combination.
+//
+// As a special exception, the copyright holders of Image Scan!'s SANE
+// backend give you permission to link Image Scan!'s SANE backend with
+// SANE frontends that communicate with Image Scan!'s SANE backend
+// solely through the SANE Application Programming Interface,
+// regardless of the license terms of these SANE frontends, and to
+// copy and distribute the resulting combined work under terms of your
+// choice, provided that every copy of the combined work is
+// accompanied by a complete copy of the source code of Image Scan!'s
+// SANE backend (the version of Image Scan!'s SANE backend used to
+// produce the combined work), being distributed under the terms of
+// the GNU General Public License plus this exception. An independent
+// module is a module which is not derived from or based on Image
+// Scan!'s SANE backend.
+//
+// As a special exception, the copyright holders of Image Scan!'s SANE
+// backend give you permission to link Image Scan!'s SANE backend with
+// independent modules that communicate with Image Scan!'s SANE
+// backend solely through the "Interpreter" interface, regardless of
+// the license terms of these independent modules, and to copy and
+// distribute the resulting combined work under terms of your choice,
+// provided that every copy of the combined work is accompanied by a
+// complete copy of the source code of Image Scan!'s SANE backend (the
+// version of Image Scan!'s SANE backend used to produce the combined
+// work), being distributed under the terms of the GNU General Public
+// License plus this exception. An independent module is a module
+// which is not derived from or based on Image Scan!'s SANE backend.
+//
+// Note that people who make modified versions of Image Scan!'s SANE
+// backend are not obligated to grant special exceptions for their
+// modified versions; it is their choice whether to do so. The GNU
+// General Public License gives permission to release a modified
+// version without this exception; this exception also makes it
+// possible to release a modified version which carries forward this
+// exception.
+
+
+#ifndef included_test_model_info_hh
+#define included_test_model_info_hh
+
+#ifndef __cplusplus
+#error "This is a C++ include file. Use a C++ compiler to compile"
+#error "code that includes this file."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "../model-info.h"
+#include <cxxtest/TestSuite.h>
+
+#include <cstdlib>
+#include <cstring>
+#include <string>
+
+#include "../list.h"
+#include "../message.h"
+#include "../get-infofile.h"
+
+struct base
+{
+ std::string dir;
+ void *cache;
+
+ void setUp (void)
+ {
+ // Log at least broken promises and unmet requirements.
+ setenv ("SANE_DEBUG_EPKOWA", "FATAL", false);
+ msg_init ();
+
+ char *srcdir = getenv ("srcdir");
+ dir = (srcdir ? srcdir : ".");
+
+ cache = NULL;
+ }
+
+ void tearDown (void)
+ {
+ cache = model_info_cache_exit (cache);
+ promise (!cache);
+ }
+};
+
+class test_model_cache_info : public CxxTest::TestSuite, public base
+{
+public:
+
+ void test_cache_life_cycle (void)
+ {
+ cache = model_info_cache_init (dir.c_str (), NULL);
+ TS_ASSERT (cache);
+
+ cache = model_info_cache_exit (cache);
+ TS_ASSERT (!cache);
+ }
+
+ void test_cache_life_cycle_status (void)
+ {
+ SANE_Status status = SANE_STATUS_NO_MEM;
+
+ cache = model_info_cache_init (dir.c_str (), &status);
+ TS_ASSERT (cache);
+
+ TS_ASSERT (SANE_STATUS_NO_MEM != status);
+
+ cache = model_info_cache_exit (cache);
+ TS_ASSERT (!cache);
+ }
+
+ void test_cache_unique_entries (void)
+ {
+ cache = model_info_cache_init (dir.c_str (), NULL);
+ TS_ASSERT (cache);
+
+ for (int i = 0; i < 10; ++i) {
+ model_info_cache_get_info ("GT-X970", NULL);
+ model_info_cache_get_info ("ES-H300", NULL);
+ }
+ TS_ASSERT_EQUALS (2, list_size (static_cast<list *> (cache)));
+ }
+
+private:
+ void setUp (void) { base::setUp (); }
+ void tearDown (void) { base::setUp (); }
+};
+
+class test_model_info : public CxxTest::TestSuite, public base
+{
+public:
+
+ void test_get_non_existent_model (void)
+ {
+ char *model = model_info_cache_get_model (bad_fw_name);
+ TS_ASSERT_EQUALS (std::string (model), std::string (bad_fw_name));
+ free (model);
+ }
+
+ void test_get_non_existent_info (void)
+ {
+ const void *info = model_info_cache_get_info (bad_fw_name, NULL);
+ TS_ASSERT (info);
+ }
+
+ void test_get_info_from_loaded_cache (void)
+ {
+ // load cache
+ model_info_cache_get_info (bad_fw_name, NULL);
+ model_info_cache_get_info ("GT-X970", NULL); // XML data
+ model_info_cache_get_info ("ES-H300", NULL); // XML data
+
+ // get info
+ const void *info = model_info_cache_get_info ("GT-X970", NULL);
+ TS_ASSERT (info);
+ TS_ASSERT (model_info_cache_get_info ("ES-H300", NULL));
+ TS_ASSERT (model_info_cache_get_info (bad_fw_name, NULL));
+ TS_ASSERT_EQUALS (info, model_info_cache_get_info ("GT-X970", NULL));
+ }
+
+ void test_get_existing_model (void)
+ {
+ setenv ("TZ", "", true); // force UTC, i.e. overseas model name
+
+ char *model = model_info_cache_get_model ("ES-H300");
+ TS_ASSERT_EQUALS (std::string ("GT-2500"), std::string (model));
+ free (model);
+ }
+
+ void test_default_values (void)
+ {
+ const void *p = model_info_cache_get_info (bad_fw_name, NULL);
+
+ const char *name = model_info_get_name (p);
+ TS_ASSERT_EQUALS (std::string (bad_fw_name), std::string (name));
+
+ const EpsonScanHard profile = model_info_get_profile (p);
+ TS_ASSERT_SAME_DATA (profile, &epson_scan_hard[0],
+ sizeof (profile));
+ }
+
+ void test_profile_equality_values (void)
+ {
+ // Identical models but for their fw_names.
+ const void *p1 = model_info_cache_get_info ("GT-10000", NULL);
+ const void *p2 = model_info_cache_get_info ("ES-6000", NULL);
+
+ TS_ASSERT_DIFFERS (p1, p2);
+ TS_ASSERT_SAME_DATA (model_info_get_profile (p1),
+ model_info_get_profile (p2),
+ sizeof (const EpsonScanHard));
+ }
+
+private:
+
+ // A firmware name that is guaranteed not to be used.
+ static const char *bad_fw_name;
+
+ void setUp (void)
+ {
+ base::setUp ();
+ cache = model_info_cache_init (dir.c_str (), NULL);
+ TS_ASSERT (cache);
+ // The ESC/I spec has a 16 byte limit on the F/W name.
+ TS_ASSERT (16 < strlen (bad_fw_name));
+ }
+
+ void tearDown (void)
+ {
+ base::tearDown ();
+ }
+};
+
+const char * test_model_info::bad_fw_name = " __ BAD F/W NAME __ ";
+
+#endif /* !defined (included_test_model_info_hh) */
diff --git a/backend/tests/test-net-obj.cc b/backend/tests/test-net-obj.cc
new file mode 100644
index 0000000..c43610d
--- /dev/null
+++ b/backend/tests/test-net-obj.cc
@@ -0,0 +1,35 @@
+/* Generated file, do not edit */
+
+#ifndef CXXTEST_RUNNING
+#define CXXTEST_RUNNING
+#endif
+
+#include <cxxtest/TestListener.h>
+#include <cxxtest/TestTracker.h>
+#include <cxxtest/TestRunner.h>
+#include <cxxtest/RealDescriptions.h>
+#include <cxxtest/ErrorPrinter.h>
+
+int main() {
+ return CxxTest::ErrorPrinter().run();
+}
+#include "test-net-obj.hh"
+
+static test_net_obj suite_test_net_obj;
+
+static CxxTest::List Tests_test_net_obj = { 0, 0 };
+CxxTest::StaticSuiteDescription suiteDescription_test_net_obj( "test-net-obj.hh", 88, "test_net_obj", suite_test_net_obj, Tests_test_net_obj );
+
+static class TestDescription_test_net_obj_test_lifecycle : public CxxTest::RealTestDescription {
+public:
+ TestDescription_test_net_obj_test_lifecycle() : CxxTest::RealTestDescription( Tests_test_net_obj, suiteDescription_test_net_obj, 92, "test_lifecycle" ) {}
+ void runTest() { suite_test_net_obj.test_lifecycle(); }
+} testDescription_test_net_obj_test_lifecycle;
+
+static class TestDescription_test_net_obj_test_missing_program : public CxxTest::RealTestDescription {
+public:
+ TestDescription_test_net_obj_test_missing_program() : CxxTest::RealTestDescription( Tests_test_net_obj, suiteDescription_test_net_obj, 100, "test_missing_program" ) {}
+ void runTest() { suite_test_net_obj.test_missing_program(); }
+} testDescription_test_net_obj_test_missing_program;
+
+#include <cxxtest/Root.cpp>
diff --git a/backend/tests/test-net-obj.hh b/backend/tests/test-net-obj.hh
new file mode 100644
index 0000000..ef0c557
--- /dev/null
+++ b/backend/tests/test-net-obj.hh
@@ -0,0 +1,126 @@
+// test-net-obj.hh -- test suite for network objects
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// License: GPLv2+|iscan
+// Authors: AVASYS CORPORATION
+//
+// This file is part of Image Scan!'s SANE backend test suite.
+//
+// Image Scan!'s SANE backend test suite is free software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You ought to have received a copy of the GNU General Public License
+// along with this package. If not, see <http://www.gnu.org/licenses/>.
+//
+//
+// Linking Image Scan!'s SANE backend statically or dynamically with
+// other modules is making a combined work based on this SANE backend.
+// Thus, the terms and conditions of the GNU General Public License
+// cover the whole combination.
+//
+// As a special exception, the copyright holders of Image Scan!'s SANE
+// backend give you permission to link Image Scan!'s SANE backend with
+// SANE frontends that communicate with Image Scan!'s SANE backend
+// solely through the SANE Application Programming Interface,
+// regardless of the license terms of these SANE frontends, and to
+// copy and distribute the resulting combined work under terms of your
+// choice, provided that every copy of the combined work is
+// accompanied by a complete copy of the source code of Image Scan!'s
+// SANE backend (the version of Image Scan!'s SANE backend used to
+// produce the combined work), being distributed under the terms of
+// the GNU General Public License plus this exception. An independent
+// module is a module which is not derived from or based on Image
+// Scan!'s SANE backend.
+//
+// As a special exception, the copyright holders of Image Scan!'s SANE
+// backend give you permission to link Image Scan!'s SANE backend with
+// independent modules that communicate with Image Scan!'s SANE
+// backend solely through the "Interpreter" interface, regardless of
+// the license terms of these independent modules, and to copy and
+// distribute the resulting combined work under terms of your choice,
+// provided that every copy of the combined work is accompanied by a
+// complete copy of the source code of Image Scan!'s SANE backend (the
+// version of Image Scan!'s SANE backend used to produce the combined
+// work), being distributed under the terms of the GNU General Public
+// License plus this exception. An independent module is a module
+// which is not derived from or based on Image Scan!'s SANE backend.
+//
+// Note that people who make modified versions of Image Scan!'s SANE
+// backend are not obligated to grant special exceptions for their
+// modified versions; it is their choice whether to do so. The GNU
+// General Public License gives permission to release a modified
+// version without this exception; this exception also makes it
+// possible to release a modified version which carries forward this
+// exception.
+
+
+#ifndef included_test_net_obj_hh
+#define included_test_net_obj_hh
+
+#ifndef __cplusplus
+#error "This is a C++ include file. Use a C++ compiler to compile"
+#error "code that includes this file."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "../net-obj.h"
+
+#include <cxxtest/TestSuite.h>
+
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <unistd.h>
+
+#include "../message.h"
+
+
+class test_net_obj : public CxxTest::TestSuite
+{
+public:
+
+ void test_lifecycle (void)
+ {
+ void *net = net_init (get_current_dir_name (), NULL);
+ TS_ASSERT (net);
+ net = net_exit (net);
+ TS_ASSERT (!net);
+ }
+
+ void test_missing_program (void)
+ {
+ char dirname_template[] = "network-XXXXXX";
+ char *dirname = mkdtemp (dirname_template);
+ TS_ASSERT (dirname);
+
+ void *net = net_init (dirname, NULL);
+ TS_ASSERT (!net);
+ net = net_exit (net);
+ TS_ASSERT (!net);
+
+ if (0 != rmdir (dirname))
+ {
+ err_minor ("%s: %s", dirname, strerror (errno));
+ }
+ }
+
+private:
+
+ void setUp (void)
+ {
+ msg_init ();
+ }
+};
+
+
+#endif /* !defined (included_test_net_obj_hh) */
diff --git a/backend/tests/xmltest-runner.sh b/backend/tests/xmltest-runner.sh
new file mode 100755
index 0000000..8bc3383
--- /dev/null
+++ b/backend/tests/xmltest-runner.sh
@@ -0,0 +1,87 @@
+#! /bin/sh
+
+test_result=PASS
+
+run_test () {
+ ./xmltest "$@"
+ if test 0 = $?; then
+ echo "PASS: xmltest $@"
+ else
+ echo "FAIL: xmltest $@"
+ test_result=FAIL
+ fi
+}
+
+run_test GT-X970
+run_test PM-A820
+run_test EP-210F
+run_test GT-X970 ES-H300
+run_test GT-X970 LP-M5600
+run_test LP-M5600 PM-A820
+run_test PM-A820 GT-X970
+run_test GT-X970 GT-X970
+run_test LP-M5600 LP-M5600
+run_test EP-210F hoge
+run_test GT-X970 hoge
+run_test PM-A820 MAN
+run_test EP-210F ES-H300
+run_test EP-210F LP-M5600
+run_test GT-X970 ES-H300 Perfection610
+run_test GT-X970 ES-H300 PM-A820
+run_test GT-X970 ES-H300 MAN
+run_test GT-X970 LP-M5600 Perfection610
+run_test GT-X970 LP-M5600 CX4600
+run_test GT-X970 LP-M5600 EP-210F
+run_test ES-H300 hoge GT-X970
+run_test ES-H300 hoge PM-A820
+run_test ES-H300 hoge EP-210F
+run_test PM-A820 LP-M5600 CX4600
+run_test PM-A820 LP-M5600 GT-X970
+run_test PM-A820 LP-M5600 MAN
+run_test LP-M5600 Perfection610 GT-X970
+run_test LP-M5600 Perfection610 PM-A820
+run_test LP-M5600 Perfection610 EP-210F
+run_test LP-M5600 EP-210F GT-X970
+run_test LP-M5600 EP-210F CX4600
+run_test LP-M5600 EP-210F MAN
+run_test EP-210F MAN hoge
+run_test EP-210F MAN ES-H300
+run_test EP-210F MAN LP-M5600
+run_test EP-210F GT-X970 ES-H300
+run_test EP-210F GT-X970 PM-A820
+run_test EP-210F GT-X970 hoge
+run_test EP-210F CX4600 Perfection610
+run_test EP-210F CX4600 PM-A820
+run_test EP-210F CX4600 hoge
+run_test ES-H300 ES-H300 ES-H300
+run_test PM-A820 PM-A820 PM-A820
+run_test GT-X970 GT-X970 CX4600
+run_test Perfection610 Perfection610 MAN
+run_test Perfection610 Perfection610 GT-X970
+run_test LP-M5600 LP-M5600 GT-X970
+run_test LP-M5600 LP-M5600 PM-A820
+run_test CX4600 CX4600 hoge
+run_test EP-210F EP-210F ES-H300
+run_test EP-210F EP-210F LP-M5600
+run_test EP-210F EP-210F MAN
+run_test ES-H300 GT-X970 GT-X970
+run_test ES-H300 PM-A820 PM-A820
+run_test GT-X970 hoge hoge
+run_test CX4600 Perfection610 Perfection610
+run_test PM-A820 LP-M5600 LP-M5600
+run_test LP-M5600 MAN MAN
+run_test hoge GT-X970 GT-X970
+run_test MAN PM-A820 PM-A820
+run_test hoge EP-210F EP-210F
+run_test GT-X970 CX4600 GT-X970
+run_test GT-X970 ES-H300 GT-X970
+run_test GT-X970 EP-210F GT-X970
+run_test PM-A820 ES-H300 PM-A820
+run_test LP-M5600 CX4600 LP-M5600
+run_test PM-A820 MAN PM-A820
+run_test hoge GT-X970 hoge
+run_test EP-210F CX4600 EP-210F
+run_test MAN hoge MAN
+
+test PASS = "$test_result"
+exit $?
diff --git a/backend/tests/xmltest.c b/backend/tests/xmltest.c
new file mode 100644
index 0000000..0f3a277
--- /dev/null
+++ b/backend/tests/xmltest.c
@@ -0,0 +1,247 @@
+/* xmltest.c -- simple model cache info checker
+ * Copyright (C) 2010 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: SEIKO EPSON CORPORATION
+ * AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "xmltest.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "../list.h"
+#include "../message.h"
+#include "../model-info.h"
+#include "../utils.h"
+
+static bool check_cache_content (list *info);
+static bool check_info_content (const _model_info_t *info);
+static bool model_info_test_cmp (const _model_info_t *info,
+ const _model_info_t_test *reference);
+
+int
+main (int argc, char** argv)
+{
+ bool pass = false;
+ list *cache = NULL;
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ /* Log at least broken promises and unmet requirements.
+ */
+ setenv ("SANE_DEBUG_EPKOWA", "FATAL", false);
+ msg_init ();
+
+ cache = (list *) model_info_cache_init (getenv ("srcdir"), &status);
+ if (!cache)
+ {
+ err_fatal ("cannot initialise model info cache (%s)",
+ sane_strstatus (status));
+ return EXIT_FAILURE;
+ }
+
+ while (--argc && ++argv)
+ {
+ const void *info = model_info_cache_get_info (*argv, &status);
+ if (!info)
+ {
+ err_major ("cannot get info for '%s' (%s)", *argv,
+ sane_strstatus (status));
+ }
+ }
+
+ pass = check_cache_content (cache);
+ cache = model_info_cache_exit (cache);
+
+ return (pass ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+/*! \brief Loops over all cache entries and checks each one of them.
+ */
+static bool
+check_cache_content (list *cache)
+{
+ bool pass = true;
+ _model_info_t *info;
+
+ list_reset (cache);
+ while ((info = list_next (cache)))
+ {
+ pass &= check_info_content (info);
+ }
+ return pass;
+}
+
+/*! \brief Checks a cache entry against known good data.
+ *
+ * \note Unknown entries are skipped.
+ */
+static bool
+check_info_content (const _model_info_t *info)
+{
+ if (0 == strcmp_c (info->fw_name, gt_x970.fw_name))
+ return model_info_test_cmp (info, &gt_x970);
+
+ if (0 == strcmp_c (info->fw_name, es_h300.fw_name))
+ return model_info_test_cmp (info, &es_h300);
+
+ if (0 == strcmp_c (info->fw_name, perfection_610.fw_name))
+ return model_info_test_cmp (info, &perfection_610);
+
+ if (0 == strcmp_c (info->fw_name, lp_m5600.fw_name))
+ return model_info_test_cmp (info, &lp_m5600);
+
+ if (0 == strcmp_c (info->fw_name, pm_a820.fw_name))
+ return model_info_test_cmp (info, &pm_a820);
+
+ if (0 == strcmp_c (info->fw_name, cx_4600.fw_name))
+ return model_info_test_cmp (info, &cx_4600);
+
+ /* cannot do remaining tests */
+ printf (" SKIP: unexpected fw_name (%s)\n", info->fw_name);
+ return true;
+}
+
+/*! \brief Compares \a info against a \a reference.
+ */
+static bool
+model_info_test_cmp (const _model_info_t *info,
+ const _model_info_t_test *reference)
+{
+ bool pass = true;
+ int i, j;
+
+ require (info && reference);
+
+ /* Compare model names */
+ if (0 != strcmp_c (info->overseas, reference->overseas))
+ {
+ pass = false;
+ printf ("FAIL: overseas -> %s != %s\n", info->overseas,
+ reference->overseas);
+ }
+
+ if (0 != strcmp_c (info->japan, reference->japan))
+ {
+ pass = false;
+ printf ("FAIL: japan -> %s != %s\n", info->japan, reference->japan);
+ }
+
+ /* Compare color profiles */
+ for (i = 0; i < 4; i++)
+ {
+ for (j = 0; j < 9; j++)
+ {
+ if (info->profile->color_profile[i][j]
+ != reference->profile.color_profile[i][j])
+ {
+ pass = false;
+ printf ("FAIL: profile[%i][%i] -> %f != %f\n", i, j,
+ info->profile->color_profile[i][j],
+ reference->profile.color_profile[i][j]);
+ }
+ }
+ }
+
+ /* Compare custom command entries */
+ if (info->command->set_focus_position
+ != reference->command.set_focus_position)
+ {
+ pass = false;
+ printf ("FAIL: focus -> %d != %d\n", info->command->set_focus_position,
+ reference->command.set_focus_position);
+ }
+
+ if (info->command->feed != reference->command.feed)
+ {
+ pass = false;
+ printf ("FAIL: feed -> %d != %d\n", info->command->feed,
+ reference->command.feed);
+ }
+
+ if (info->command->eject != reference->command.eject)
+ {
+ pass = false;
+ printf ("FAIL: eject -> %d != %d\n", info->command->eject,
+ reference->command.eject);
+ }
+
+ if (info->command->lock != reference->command.lock)
+ {
+ pass = false;
+ printf ("FAIL: lock -> %d != %d\n", info->command->lock,
+ reference->command.lock);
+ }
+
+ if (info->command->unlock != reference->command.unlock)
+ {
+ pass = false;
+ printf ("FAIL: unlock -> %d != %d\n", info->command->unlock,
+ reference->command.lock);
+ }
+
+ return pass;
+}
diff --git a/backend/tests/xmltest.h b/backend/tests/xmltest.h
new file mode 100644
index 0000000..02404e1
--- /dev/null
+++ b/backend/tests/xmltest.h
@@ -0,0 +1,190 @@
+/* xmltest.c -- simple model cache info checker
+ * Copyright (C) 2010 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: SEIKO EPSON CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifndef included_xmltest_h
+#define included_xmltest_h
+
+#include "../get-infofile.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+typedef struct
+{
+ char *fw_name; /* key for _model_info_cache_get_info */
+ char *overseas; /* model name */
+ char *japan; /* model name */
+ char *name; /* points to one of overseas, japan or
+ * fw_name, never NULL */
+ scan_command_t *command; /* command customisation info */
+ EpsonScanHard profile; /* colour profiles */
+
+ bool from_file; /* origin of our data, used to control
+ * which members need to be free()d at
+ * destruction time */
+
+} _model_info_t;
+
+typedef struct
+{
+ char *fw_name; /* key for _model_info_cache_get_info */
+ char *overseas; /* model name */
+ char *japan; /* model name */
+
+ scan_command_t command; /* command customisation info */
+ EpsonScanHardRec profile; /* colour profiles */
+
+} _model_info_t_test;
+
+const _model_info_t_test gt_x970 = {
+ "GT-X970",
+ NULL,
+ NULL,
+
+ {0x01, 0, 0, 0xFF, true, true},
+ {0x87,
+ {{1.1978,-0.1417,-0.0561,-0.0852, 1.1610,-0.0758,-0.0395,-0.3212, 1.3607},
+ {1.0000, 0.0009,-0.0009,-0.1268, 1.0523, 0.0745,-0.0075,-0.0873, 1.0948},
+ {1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000},
+ {1.4475,-0.3957,-0.0518,-0.0138, 1.0644,-0.0506,-0.0199,-0.2050, 1.2249}}},
+
+};
+
+const _model_info_t_test es_h300 = {
+ "ES-H300",
+ "GT-2500",
+ "ES-H300",
+
+ {0x01, 0, 0, 0xFF, false, false},
+ {0x87,
+ {{1.0359,-0.0146,-0.0213,-0.0752, 1.0963,-0.0211,-0.0456,-0.3238, 1.3693},
+ {1, 0, 0, 0, 1, 0, 0, 0, 1},
+ {1, 0, 0, 0, 1, 0, 0, 0, 1},
+ {1, 0, 0, 0, 1, 0, 0, 0, 1}}},
+
+};
+
+const _model_info_t_test perfection_610 = {
+ "Perfection610",
+ "Perfection 610",
+ NULL,
+
+ {0x01, 0, 0, 0xFF, false, false},
+ {0x87,
+ {{1.1442,-0.0705,-0.0737,-0.0702, 1.1013,-0.0311,-0.0080,-0.3588, 1.3668},
+ {1, 0, 0, 0, 1, 0, 0, 0, 1},
+ {1, 0, 0, 0, 1, 0, 0, 0, 1},
+ {1, 0, 0, 0, 1, 0, 0, 0, 1}}},
+
+};
+
+const _model_info_t_test lp_m5600 = {
+ "LP-M5600",
+ NULL,
+ "LP-M5600",
+
+ {0x01, 0, 0x19, 0xFF, false, false},
+ {0x87,
+ {{1.0784,-0.0560,-0.0224,-0.1793, 1.2234,-0.0441,-0.0041,-0.2636, 1.2677},
+ {1, 0, 0, 0, 1, 0, 0, 0, 1},
+ {1, 0, 0, 0, 1, 0, 0, 0, 1},
+ {1, 0, 0, 0, 1, 0, 0, 0, 1}}},
+
+};
+
+const _model_info_t_test pm_a820 = {
+ "PM-A820",
+ "Stylus Photo RX560/RX580/RX590",
+ "PM-A820",
+
+ {0x01, 0, 0, 0xFF, false, false},
+ {0x87,
+ {{0.9533, 0.0885,-0.0418, 0.0033, 1.0627,-0.0660,-0.0137,-0.1904, 1.2041},
+ {1, 0, 0, 0, 1, 0, 0, 0, 1},
+ {1, 0, 0, 0, 1, 0, 0, 0, 1},
+ {1, 0, 0, 0, 1, 0, 0, 0, 1}}},
+
+};
+
+const _model_info_t_test cx_4600 = {
+ "CX4600",
+ "Stylus CX4500/CX4600",
+ NULL,
+
+ {0x01, 0, 0, 0xFF, false, false},
+ {0x87,
+ {{0.9828, 0.0924,-0.0752, 0.0255, 1.151,-0.1765, 0.0049,-0.325, 1.3201},
+ {1, 0, 0, 0, 1, 0, 0, 0, 1},
+ {1, 0, 0, 0, 1, 0, 0, 0, 1},
+ {1, 0, 0, 0, 1, 0, 0, 0, 1}}},
+
+};
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* !defined (included_xmltest_h) */
diff --git a/backend/timing.c b/backend/timing.c
new file mode 100644
index 0000000..1b370ce
--- /dev/null
+++ b/backend/timing.c
@@ -0,0 +1,188 @@
+/* timing.c -- optional support for run-time time stamp collection
+ * Copyright (C) 2009 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "timing.h"
+
+#if ENABLE_TIMING
+
+#include <math.h>
+#include <string.h>
+
+size_t time_pass_count = 0;
+
+struct time_interval_ time_scan;
+struct time_interval_ time_pass[TIME_PASS_MAX];
+
+static const long NANOS_PER_SEC = 1000000000;
+
+static void
+time_compute_span (struct time_interval_ *m)
+{
+ m->span.t.tv_nsec = m->stop.t.tv_nsec - m->start.t.tv_nsec;
+ if (0 > m->span.t.tv_nsec)
+ {
+ m->span.t.tv_nsec += NANOS_PER_SEC;
+ m->stop.t.tv_sec -= 1;
+ }
+ m->span.t.tv_sec = m->stop.t.tv_sec - m->start.t.tv_sec;
+
+ m->span.is_valid = m->start.is_valid && m->stop.is_valid;
+}
+
+void
+time_clear (void)
+{
+ memset (&time_scan, 0, sizeof (time_scan));
+ memset ( time_pass, 0, sizeof (*time_pass) * TIME_PASS_MAX);
+}
+
+static double
+time_stamp_to_double (const struct time_stamp_ *ts)
+{
+ return ((double) ts->t.tv_nsec) / NANOS_PER_SEC + ts->t.tv_sec;
+}
+
+static void
+time_fprintf_interval (FILE *stream, const struct time_interval_ *ti,
+ const char *prefix)
+{
+ char str[512];
+
+ if (ti->span.is_valid)
+ snprintf (str, num_of (str), "%s: %f (start: %f, stop: %f)", prefix,
+ time_stamp_to_double (&ti->span),
+ time_stamp_to_double (&ti->start),
+ time_stamp_to_double (&ti->stop));
+ else if (ti->start.is_valid)
+ snprintf (str, num_of (str), "%s: --- (start: %f, stop: ---)", prefix,
+ time_stamp_to_double (&ti->start));
+ else if (ti->stop.is_valid)
+ snprintf (str, num_of (str), "%s: --- (start: ---, stop: %f)", prefix,
+ time_stamp_to_double (&ti->stop));
+ else
+ snprintf (str, num_of (str), "%s: --- (start: ---, stop: ---)", prefix);
+
+ fprintf (stream, "%s\n", str);
+}
+
+/* There is a script in utils/ that can combine the results of
+ multiple scans (in one or more SANE frontend sessions) into
+ a single CSV file.
+ */
+void
+time_stats (size_t count)
+{
+ size_t i, n = 0;
+ double t, sum = 0, sum_sq = 0;
+
+ fprintf (stderr, "\f\n"); /* start a new form */
+ fprintf (stderr, "Per pass timing data (in seconds)\n");
+ for (i = 0; i < count && i < TIME_PASS_MAX; ++i)
+ {
+ time_compute_span (&time_pass[i]);
+ time_fprintf_interval (stderr, &time_pass[i], "pass");
+ }
+ fprintf (stderr, "\n");
+
+ fprintf (stderr, "Scan timing data (in seconds)\n");
+ time_compute_span (&time_scan);
+ time_fprintf_interval (stderr, &time_scan, "scan");
+ fprintf (stderr, "\n");
+
+ if (time_scan.span.is_valid)
+ {
+ if (0 < count)
+ fprintf (stderr, "Scan avg: %f s/pass\n",
+ time_stamp_to_double (&time_scan.span) / count);
+ }
+
+ for (i = 0; i < count && i < TIME_PASS_MAX; ++i)
+ {
+ if (time_pass[i].span.is_valid)
+ {
+ ++n;
+ t = time_stamp_to_double (&time_pass[i].span);
+ sum += t;
+ sum_sq += (t * t);
+ }
+ }
+
+ if (0 != n)
+ {
+ fprintf (stderr, "Pass sum: %f s, %zd passes\n", sum, n);
+ fprintf (stderr, "Pass avg: %f", sum / n);
+ if (1 < n) /* add standard deviation */
+ {
+ fprintf (stderr, " (+/- %f)",
+ sqrt ((n * sum_sq - sum * sum) / n / (n-1)));
+ }
+ fprintf (stderr, " s/pass\n");
+ }
+}
+
+#endif /* ENABLE_TIMING */
diff --git a/backend/timing.h b/backend/timing.h
new file mode 100644
index 0000000..8181cda
--- /dev/null
+++ b/backend/timing.h
@@ -0,0 +1,119 @@
+/* timing.h -- optional support for run-time time stamp collection
+ * Copyright (C) 2009 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifndef timing_h_included
+#define timing_h_included
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#ifndef ENABLE_TIMING
+#define ENABLE_TIMING 0
+#endif
+
+#define TIME_PASS_MAX 100
+extern size_t time_pass_count;
+
+#if !ENABLE_TIMING /* turn API calls into no-ops */
+
+#define time_clear() do {} while (0)
+#define time_stamp(interval,end) do {} while (0)
+#define time_stats(count) do {} while (0)
+
+#else /* do something useful */
+
+#include <time.h>
+#include "defines.h"
+
+struct time_stamp_
+{
+ bool is_valid;
+ struct timespec t;
+};
+
+struct time_interval_
+{
+ struct time_stamp_ start;
+ struct time_stamp_ stop;
+ struct time_stamp_ span;
+};
+
+extern struct time_interval_ time_scan;
+extern struct time_interval_ time_pass[TIME_PASS_MAX];
+
+#define time_stamp(interval,end) \
+ do { \
+ interval.end.is_valid = (0 == clock_gettime (CLOCK_MONOTONIC, \
+ &(interval.end.t))); \
+ } while (0)
+
+void time_clear (void);
+void time_stats (size_t count);
+
+#endif /* ENABLE_TIMING */
+
+#endif /* !defined (timing_h_included) */
diff --git a/backend/utils.c b/backend/utils.c
new file mode 100644
index 0000000..e43afc1
--- /dev/null
+++ b/backend/utils.c
@@ -0,0 +1,334 @@
+/* utils.c -- assorted utility functions and macros
+ * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "utils.h"
+
+#include <errno.h>
+#include <string.h>
+#include <time.h> /* nanosleep */
+
+
+/*! Creates a string of 2-character hexadecimal values from a NUL
+ terminated character string.
+
+ The implementation acquires the memory needed for the result and
+ the caller is responsible for releasing it.
+
+ \return a pointer to the encoded string or \c NULL if the memory
+ required could not be acquired
+ */
+char *
+fw_name_to_hex (const char *fw_name) /* '\0' terminated */
+{
+ char *rv, *p;
+
+ log_call();
+
+ if (!fw_name) return NULL; /* guard clause */
+
+ p = rv = t_malloc (2 * strlen (fw_name) + 1, char);
+ if (!rv) return NULL;
+
+ while ('\0' != *fw_name)
+ {
+ sprintf (p, "%02x", *fw_name);
+ p += 2;
+ ++fw_name;
+ }
+ *p = '\0';
+ return rv;
+}
+
+/*! Sets up a properly initialised resolution info object.
+ */
+void
+init_resolution_info (resolution_info *self, u_char *data)
+{
+ SANE_Bool (*cond) (const u_char *) = NULL;
+ size_t step = 0;
+
+ if (!self) return;
+
+ self->last = 0; /* set defaults */
+ self->size = -1;
+ self->list = NULL;
+ self->deep = SANE_TRUE;
+
+ if (!data) return; /* act like a default constructor */
+
+ self->size = 0;
+ self->list = t_realloc (NULL, self->size + 1, SANE_Word);
+
+ if (!self->list)
+ {
+ err_major ("%s", strerror (ENOMEM));
+ self->size = -1;
+ return;
+ }
+
+ if ('R' == data[0]) /* ESC I data block */
+ {
+ cond = resolution_info_ESC_I_cond;
+ step = 3;
+ }
+ else /* ESC i data block, hopefully */
+ {
+ cond = resolution_info_ESC_i_cond;
+ step = 2;
+ }
+
+ while (cond (data))
+ {
+ void *p = self->list;
+
+ self->size++;
+ self->list = t_realloc (p, self->size + 1, SANE_Word);
+ if (!self->list)
+ {
+ delete (p);
+
+ err_major ("%s", strerror (ENOMEM));
+ self->size = -1;
+ return;
+ }
+ self->list[self->size] = data[step - 1] << 8 | data[step - 2];
+ data += step;
+ log_info ("resolution: %d dpi", self->list[self->size]);
+ }
+ self->list[0] = self->size;
+}
+
+/*! Releases resources held by \a self and resets it to default state.
+ */
+void
+free_resolution_info (resolution_info *self)
+{
+ if (!self) return;
+
+ if (self->deep)
+ delete (self->list);
+
+ init_resolution_info (self, NULL);
+}
+
+/*! Makes an optionally deep copy of \a src to \a dest.
+
+ Any resources held by \a dest will be returned to the system.
+ */
+SANE_Status
+copy_resolution_info (resolution_info *dest, const resolution_info *src,
+ SANE_Bool deep)
+{
+ if (!dest || !src) return SANE_STATUS_INVAL;
+
+ require (!src->list || src->size == src->list[0]);
+
+ if (deep && src->list) /* copy resolution list */
+ {
+ size_t size = (src->size + 1) * sizeof (SANE_Word);
+ SANE_Word *list = t_malloc (size, SANE_Word);
+
+ if (!list) return SANE_STATUS_NO_MEM;
+
+ memcpy (list, src->list, size);
+
+ if (dest->deep)
+ delete (dest->list);
+ dest->list = list;
+ }
+ else /* just refer to it */
+ {
+ if (dest->deep)
+ delete (dest->list);
+ dest->list = src->list;
+ }
+
+ dest->last = src->last;
+ dest->size = src->size;
+ dest->deep = deep;
+
+ promise (!dest->list || dest->size == dest->list[0]);
+
+ return SANE_STATUS_GOOD;
+}
+
+
+void
+_update_ranges (const device *hw, extension *src)
+{
+ require (hw);
+ require (src);
+
+ src->x_range.min = 0;
+ src->x_range.max = SANE_FIX (src->max_x * MM_PER_INCH / hw->base_res);
+ src->x_range.quant = 0;
+
+ src->y_range.min = 0;
+ src->y_range.max = SANE_FIX (src->max_y * MM_PER_INCH / hw->base_res);
+ src->y_range.quant = 0;
+
+ if (!hw->cmd->request_identity2) return;
+
+ /* correct for color shuffle offsets */
+ src->y_range.max = SANE_FIX ((src->max_y - 2 * hw->max_line_distance)
+ * MM_PER_INCH / hw->base_res);
+}
+
+
+/*! Convenience type to hold document size information.
+ */
+struct _doc_size_info
+{
+ const double width;
+ const double height;
+ const char *label;
+};
+
+/*! Document size information for known sizes.
+ */
+static const struct _doc_size_info
+doc_size[] = {
+ /* second byte bit flag values */
+ { 182.00, 257.00, "B5V"},
+ { 257.00, 182.00, "B5H"},
+ { 148.00, 210.00, "A5V"},
+ { 210.00, 148.00, "A5H"},
+ { 184.15, 266.70, "EXV"},
+ { 266.70, 184.15, "EXH"},
+ { 0 , 0 , "RSV"}, /* reserved */
+ { 0 , 0 , "UNK"}, /* unknown */
+ /* first byte bit flag values */
+ { 297.00, 420.00, "A3V"},
+ { 279.40, 431.80, "WLT"},
+ { 257.00, 364.00, "B4V"},
+ { 215.90, 355.60, "LGV"},
+ { 210.00, 297.00, "A4V"},
+ { 297.00, 210.00, "A4H"},
+ { 215.90, 279.40, "LTV"},
+ { 279.40, 215.90, "LTH"},
+};
+
+
+void
+_update_doc_size (extension *src, uint16_t value)
+{
+ const uint16_t DOC_MASK = ~0x0200;
+
+ size_t i = 0;
+
+ require (src);
+
+ if ((DOC_MASK & value) != value)
+ {
+ err_minor ("clearing reserved bit flags to match spec");
+ value &= DOC_MASK;
+ }
+
+ if (0 == value) /* size detection not supported */
+ {
+ src->doc_x = 0;
+ src->doc_y = 0;
+ return;
+ }
+
+ while (!(0x8000 & value) && (num_of (doc_size) > i))
+ {
+ value = value << 1;
+ ++i;
+ }
+
+ if (0 != strcmp_c ("UNK", doc_size[i].label))
+ {
+ src->doc_x = doc_size[i].width;
+ src->doc_y = doc_size[i].height;
+ }
+ else
+ {
+ src->doc_x = SANE_UNFIX (src->x_range.max);
+ src->doc_y = SANE_UNFIX (src->y_range.max);
+ }
+
+ value = value << 1;
+ if (0 != value)
+ {
+ err_minor ("device detected multiple document sizes!\n");
+ }
+
+ log_info ("detected document size: %s (%.2fmm x %.2fmm)",
+ doc_size[i].label, src->doc_x, src->doc_y);
+}
+
+int
+microsleep (size_t usec)
+{
+ struct timespec ts;
+ ts.tv_sec = usec / 1000000;
+ ts.tv_nsec = (usec % 1000000) * 1000;
+
+ return nanosleep (&ts, NULL);
+}
diff --git a/backend/utils.h b/backend/utils.h
new file mode 100644
index 0000000..7b1e18f
--- /dev/null
+++ b/backend/utils.h
@@ -0,0 +1,199 @@
+/* utils.h -- assorted utility functions and macros
+ * Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifndef utils_h_included
+#define utils_h_included
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "device.h"
+
+
+/*! \brief Encodes a fw_name as a string of hexadecimals.
+ */
+char * fw_name_to_hex (const char *fw_name);
+
+/*! \brief Converts a buffer to an unsigned 32-bit integer.
+ */
+inline static uint32_t
+buf_to_uint32 (const byte *buf)
+{
+ return buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0];
+}
+
+/*! \brief Converts a buffer to an unsigned 16-bit integer.
+ */
+inline static uint16_t
+buf_to_uint16 (const byte *buf)
+{
+ return buf[1] << 8 | buf[0];
+}
+
+/*! \brief Converts an unsigned 32-bit integer to a buffer.
+ */
+inline static void
+uint32_to_buf (uint32_t val, byte *buf)
+{
+ if (!buf) return;
+
+ buf[0] = val;
+ buf[1] = val >> 8;
+ buf[2] = val >> 16;
+ buf[3] = val >> 24;
+}
+
+/*! \brief Converts an unsigned 16-bit integer to a buffer.
+ */
+inline static void
+uint16_to_buf (uint16_t val, byte *buf)
+{
+ if (!buf) return;
+
+ buf[0] = val;
+ buf[1] = val >> 8;
+}
+
+
+/* Scan area related queries and computations.
+ */
+
+/*! \brief Tells whether an extension supports document size detection.
+ */
+inline static SANE_Bool
+has_size_check_support (const extension *src)
+{
+ return src->has_size_check;
+}
+
+/*! \brief Recomputes an extension's scan area dimensions (in mm).
+ \hideinitializer
+ */
+#define update_ranges(hw,src) _update_ranges (hw, (extension *) src)
+
+void _update_ranges (const device *hw, extension *src);
+
+/*! \brief Re-establishes the detected document size.
+ \hideinitializer
+ */
+#define update_doc_size(src,value) _update_doc_size ((extension *) src, value)
+
+void _update_doc_size (extension *src, uint16_t value);
+
+
+/* Resolution information handlers.
+ */
+
+void init_resolution_info (resolution_info *self, byte *data);
+void free_resolution_info (resolution_info *self);
+SANE_Status copy_resolution_info (resolution_info *dest,
+ const resolution_info *src, SANE_Bool deep);
+
+/*! \brief Tells whether we are looking at a resolution in an ESC I reply.
+ */
+inline static SANE_Bool
+resolution_info_ESC_I_cond (const u_char *data)
+{
+ return ('R' == data[0]);
+}
+
+/*! \brief Tells whether we are looking at a resolution in an ESC i reply.
+ */
+inline static SANE_Bool
+resolution_info_ESC_i_cond (const u_char *data)
+{
+ return (0 != data[0] || 0 != data[1]);
+}
+
+/*! \brief A wrapper around strcmp that checks for NULL strings
+ */
+inline static int
+strcmp_c (const char *s1, const char *s2)
+{
+ if (!s1 && !s2) return 0;
+ if (s1 && !s2) return 1;
+ if (!s1 && s2) return -1;
+
+ return strcmp (s1, s2);
+}
+
+/*! \brief A wrapper around strncmp that checks for NULL strings
+ */
+inline static int
+strncmp_c (const char *s1, const char *s2, size_t n)
+{
+ if (!s1 && !s2) return 0;
+ if (s1 && !s2) return 1;
+ if (!s1 && s2) return -1;
+
+ return strncmp (s1, s2, n);
+}
+
+int microsleep (size_t usec);
+
+
+#endif /* !defined (utils_h_included) */
diff --git a/backend/xmlreader.c b/backend/xmlreader.c
new file mode 100644
index 0000000..56e23e9
--- /dev/null
+++ b/backend/xmlreader.c
@@ -0,0 +1,378 @@
+/* xmlreader.c --
+ * Copyright (C) 2010 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: SEIKO EPSON CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <time.h>
+
+#include "utils.h"
+#include "get-infofile.h"
+#include "xmlreader.h"
+
+typedef struct devices{
+ char *firmware;
+ char *overseas;
+ char *japan;
+}devices, *devicePtr;
+
+typedef struct commands{
+ char *command_type;
+ char *command_level;
+}commands, *commandPtr;
+
+static void default_profile_set(double color_profile[9]);
+
+
+static void parseCommand(xmlNodePtr cur, scan_command_t* command);
+static unsigned char parseStatus(char *status, char *name);
+
+char *
+parseDevices(xmlNodePtr cur, char *name){
+ devicePtr ret;
+
+ char *region = NULL;
+ char *result = NULL;
+
+ log_call();
+
+ ret = t_calloc (1, devices);
+ if(ret == NULL){
+ err_major("out of memory");
+ return NULL;
+ }
+
+ cur = cur->xmlChildrenNode;
+
+ while (cur != NULL) {
+ if (!xmlStrcmp(cur->name, (const xmlChar *) "firmware")) {
+ if(ret->firmware == NULL && strcmp(name, FIRMWARE) == 0){
+ ret->firmware = (char *)xmlGetProp(cur, (const xmlChar *) "name");
+ result = strdup(ret->firmware);
+ if(!ret->firmware){
+ delete (ret);
+ return NULL;
+ }
+ break;
+ }
+ }else if (!xmlStrcmp(cur->name, (const xmlChar *) "model")) {
+ region = (char *)xmlGetProp(cur, (const xmlChar *) "region");
+ if(region && strcmp(name, MODEL_JAPAN) == 0){
+ if(strcmp(name, MODEL_JAPAN) == 0 && strcasecmp(region, "Japan") == 0){
+ ret->japan = (char *)xmlGetProp(cur, (const xmlChar *) "name");
+ result = strdup(ret->japan);
+ delete (ret->japan);
+ if(!result || isspace(result[0]) != 0 || strlen(result) == 0){
+ delete (result);
+ delete (ret);
+ delete (region);
+ result = NULL;
+ err_minor("Model has no Name.");
+ }
+ }
+ break;
+ }else if(!region && strcmp(name, MODEL_OVERSEAS) == 0){
+ ret->overseas = (char *)xmlGetProp(cur, (const xmlChar *) "name");
+ result = strdup(ret->overseas);
+ delete(ret->overseas);
+ if(!result || isspace(result[0]) != 0 || strlen(result) == 0){
+ delete (result);
+ delete (ret);
+ delete (region);
+ result = NULL;
+ err_minor("Model has no Name.");
+ }
+ break;
+ }
+ delete (region);
+ }
+ cur = cur->next;
+ }
+
+ delete (region);
+ delete (ret);
+
+ return result;
+}
+
+EpsonScanHard
+parseProfiles(xmlNodePtr cur){
+ EpsonScanHard ret;
+ int i, j;
+ char *tmp;
+ char pmat[9][3] = {"rr", "rg", "rb",
+ "gr", "gg", "gb",
+ "br", "bg", "bb"};
+ char *profile_type;
+ xmlNodePtr curtmp;
+
+ log_call();
+
+ ret = t_calloc (1, EpsonScanHardRec);
+ if(ret == NULL){
+ err_major("out of memory");
+ return NULL;
+ }
+
+ /*add default value*/
+ for(i = 0; i < 4; i++){
+ default_profile_set(ret->color_profile[i]);
+ }
+
+ cur = curtmp = cur->xmlChildrenNode;
+ while(cur != NULL){
+ if ((!xmlStrcmp(cur->name, (const xmlChar *) "profile"))){
+ curtmp = cur;
+ profile_type = (char *)xmlGetProp(cur, (const xmlChar *) "type");
+
+ if(strcmp(profile_type, "reflective") == 0){
+ i = 0;
+ }else if(strcmp(profile_type, "color negative") == 0){
+ i = 1;
+ }else if(strcmp(profile_type, "monochrome negative") == 0){
+ i = 2;
+ }else if(strcmp(profile_type, "positive") == 0){
+ i = 3;
+ }else {
+ err_minor("profile of the wrong type.");
+ delete (profile_type);
+ delete (ret);
+ return NULL;
+ }
+ delete (profile_type);
+
+ j = 0;
+ cur = cur->xmlChildrenNode;
+ while(cur != NULL){
+ if(!xmlStrcmp(cur->name, (const xmlChar *)pmat[j])){
+ tmp = (char*)xmlGetProp(cur, (const xmlChar *) "value");
+ ret->color_profile[i][j] = atof(tmp);
+ delete (tmp);
+ j++;
+ }
+ cur = cur->next;
+ }
+ if(j != 9){
+ err_minor("Value that is not sufficient exists.");
+ default_profile_set(ret->color_profile[i]);
+ }
+ }
+ cur = curtmp;
+ cur = curtmp = cur->next;
+ }
+
+ return ret;
+}
+
+static
+void default_profile_set(double color_profile[9]){
+ /*default value is assigned*/
+ const double default_profile[9] =
+ {1.0000, 0.0000, 0.0000,
+ 0.0000, 1.0000, 0.0000,
+ 0.0000, 0.0000, 1.0000} ;
+ int j;
+
+ for(j = 0; j < 9; j++){
+ color_profile[j] = default_profile[j];
+ }
+}
+
+scan_command_t *
+parseCommands_set(xmlNodePtr cur){
+ commandPtr ret;
+ scan_command_t* command;
+
+ log_call();
+
+ command = t_calloc (1, scan_command_t);
+ if(command == NULL){
+ err_major("out of memory");
+ return NULL;
+ }
+
+ /*default value*/
+ command->set_focus_position = 0xFF;
+ command->feed = 0xFF;
+ command->eject = 0xFF;
+ command->lock = false;
+ command->unlock = false;
+
+ ret = t_calloc (1, commands);
+ if(ret == NULL){
+ err_major("out of memory");
+ delete (command);
+ return NULL;
+ }
+
+ ret->command_type = (char *)xmlGetProp(cur, (const xmlChar *) "type");
+
+ ret->command_level = (char *)xmlGetProp(cur, (const xmlChar *) "level");
+
+ cur = cur->xmlChildrenNode;
+ while(cur != NULL){
+ if ((!xmlStrcmp(cur->name, (const xmlChar *) "command"))){
+ parseCommand(cur, command);
+ }
+ cur = cur->next;
+ }
+
+ delete (ret->command_type);
+ delete (ret->command_level);
+ delete (ret);
+
+ return command;
+}
+
+static void
+parseCommand(xmlNodePtr cur, scan_command_t* command)
+{
+ char *tmp;
+ char *status = NULL;
+
+ status = (char *)xmlGetProp(cur, (const xmlChar *) "status");
+
+ if((tmp = (char *)xmlGetProp(cur, (const xmlChar *) "name")) != NULL){
+ if(strcmp(tmp, "set_focus_position") == 0){
+ command->set_focus_position = parseStatus(status, "set_focus_position");
+ }else if(strcmp(tmp, "feed") == 0){
+ command->feed = parseStatus(status, "feed");
+ }else if(strcmp(tmp, "eject") == 0){
+ command->eject = parseStatus(status, "eject");
+ }else if(strcmp(tmp, "lock") == 0){
+ command->lock = true;
+ if(status && strcmp(status, "disable") == 0) command->lock = false;
+ }else if(strcmp(tmp, "unlock") == 0){
+ command->unlock = true;
+ if(status && strcmp(status, "disable") == 0) command->unlock = false;
+ }
+ delete (tmp);
+ delete (status);
+ }
+}
+
+static unsigned char
+parseStatus(char *status, char *name)
+{
+ unsigned char value;
+
+ if(!status || strcmp(status, "enabled") == 0){/*enabled*/
+ if(strcmp(name, "set_focus_position") == 0) value = 0x70;
+ else if(strcmp(name, "feed") == 0) value = 0x19;
+ else if(strcmp(name, "eject") == 0) value = 0x0C;
+ else value = 0;
+ }else if(strcmp(status, "disabled") == 0){
+ value = 0;
+ }else{
+ value = 0;
+ }
+
+ return value;
+}
+
+capability_data_t*
+parseCapabilities(xmlNodePtr cur)
+{
+ capability_data_t *capabilities;
+ char *tmp;
+ char *endp;
+
+ log_call();
+
+ capabilities = t_calloc (1, capability_data_t);
+ if(capabilities == NULL){
+ err_major("out of memory");
+ return NULL;
+ }
+
+ cur = cur->xmlChildrenNode;
+ while(cur != NULL){
+ if ((!xmlStrcmp(cur->name, (const xmlChar *) "scan-area"))){
+ endp = tmp = (char *)xmlGetProp(cur, (const xmlChar *) "width");
+ capabilities->width = strtol(tmp, &endp, 10);
+ if(endp == tmp) capabilities->width = -1;
+ if(*endp != '\0')
+ err_minor("ignoring trailing garbage (%s)", endp);
+ delete (tmp);
+
+ endp = tmp = (char *)xmlGetProp(cur, (const xmlChar *) "height");
+ capabilities->height = strtol(tmp, &endp, 10);
+ if(endp == tmp) capabilities->height = -1;
+ if(*endp != '\0')
+ err_minor("ignoring trailing garbage (%s)", endp);
+ delete (tmp);
+
+ endp = tmp = (char *)xmlGetProp(cur, (const xmlChar *) "base");
+ capabilities->base = strtol(tmp, &endp, 10);
+ if(endp == tmp) capabilities->base = 1;
+ if(*endp != '\0')
+ err_minor("ignoring trailing garbage (%s)", endp);
+ delete (tmp);
+ }
+ cur = cur->next;
+ }
+
+ return capabilities;
+}
diff --git a/backend/xmlreader.h b/backend/xmlreader.h
new file mode 100644
index 0000000..cabafdf
--- /dev/null
+++ b/backend/xmlreader.h
@@ -0,0 +1,79 @@
+/* xmlreader.h --
+ * Copyright (C) 2010 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: SEIKO EPSON CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend is free software.
+ * You can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation;
+ * either version 2 of the License or at your option any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+#ifndef included_xmlreader_h
+#define included_xmlreader_h
+
+#include <libxml/xmlmemory.h>
+#include <libxml/parser.h>
+
+char *parseDevices(xmlNodePtr cur, char *name);
+
+EpsonScanHard parseProfiles(xmlNodePtr cur);
+
+scan_command_t *parseCommands_set(xmlNodePtr cur);
+
+capability_data_t *parseCapabilities(xmlNodePtr cur);
+
+#endif
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..3459e62
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,378 @@
+dnl configure.ac -- an -*- autoconf -*- template for configure
+dnl Copyright (C) 2004--2007 Olaf Meeuwissen
+dnl Copyright (C) 2019 SEIKO EPSON Corporation
+dnl
+dnl This file is part of the "Image Scan!" build infra-structure.
+dnl
+dnl The "Image Scan!" build infra-structure is free software.
+dnl You can redistribute it and/or modify it under the terms of the GNU
+dnl General Public License as published by the Free Software Foundation;
+dnl either version 2 of the License or at your option any later version.
+dnl
+dnl This program is distributed in the hope that it will be useful, but
+dnl WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+dnl FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+dnl See the GNU General Public License for more details.
+dnl
+dnl You should have received a verbatim copy of the GNU General Public
+dnl License along with this program; if not, write to:
+dnl
+dnl Free Software Foundation, Inc.
+dnl 59 Temple Place, Suite 330
+dnl Boston, MA 02111-1307 USA
+dnl
+dnl Process this file with autoconf to produce a configure script.
+
+
+AC_PREREQ(2.60)
+AC_INIT([Image Scan! for Linux],
+ [2.30.4], [linux-printer@epson.jp], [iscan])
+
+SCM_INIT([-Wall -Werror])
+
+SCM_RELEASE([2])
+
+SCM_VENDOR([SEIKO EPSON Corporation])
+SCM_AUTHOR([SEIKO EPSON Corporation])
+SCM_WEBSITE([http://download.ebz.epson.net/dsc/search/01/search/?OSC=LX])
+SCM_INFO(["simple, easy to use scanner utility for EPSON scanners"],["\
+Image Scan! is a graphical scanner utility for people that do not need\\
+all the bells and whistles provided by several of the other utilities\\
+out there (xsane, QuiteInsane, Kooka).\\
+.\\
+At the moment it only supports SEIKO EPSON scanners and all-in-ones.\\
+However, the scanner driver it provides can be used by any other SANE\\
+standard compliant scanner utility.\
+"],["\
+Note that several scanners require a non-free plugin before they can\\
+be used with this software.\
+"])
+
+AC_SUBST(DATA_PKG_NAME, "iscan-data")
+AC_SUBST(MODEL_DATA_DIR_NAME, "device")
+
+AC_CONFIG_SRCDIR([iscan.spec])
+AC_CONFIG_HEADER([config.h])
+AC_CONFIG_MACRO_DIR([m4])
+
+AM_INIT_AUTOMAKE([1.7 gnu -Wall])
+
+
+dnl checks for programs
+
+AC_PROG_CXX
+
+# Transitionary check for the C++ ABI while we are switching provided
+# binaries from gcc-3.[23] to gcc-3.4 or later. Currently gcc-3.[23]
+# is still the official compiler version to use, but experimental bi-
+# naries are released with a ".c2" suffix.
+# The CXX_ABI for gcc-3.[23] is 102, btw.
+# Note that the PACKAGE_RELEASE for the gcc-3.4 or later packages has
+# to be larger than that for the gcc-3.[23] packages (as long as both
+# are distributed) for smooth upgrade paths.
+
+if test x"$CXX" = xg++; then
+ AC_MSG_CHECKING([C++ ABI version])
+ CXX_ABI="`$CXX -E -dM - < /dev/null | $AWK '/GXX_ABI/ { print $3 }'`"
+ AC_MSG_RESULT([$CXX_ABI])
+ if test x"$CXX_ABI" = x1002; then
+ PACKAGE_CXX_ABI=".c2"
+ else
+ PACKAGE_CXX_ABI=""
+ fi
+ AC_SUBST(PACKAGE_CXX_ABI)
+fi
+
+AC_PROG_CC
+AC_PROG_CPP
+AC_PROG_INSTALL
+AC_PROG_LN_S
+AC_PROG_MAKE_SET
+
+# CxxTest unit testing support
+AC_CHECK_PROGS([CXXTESTGEN], [cxxtestgen cxxtestgen.py cxxtestgen.pl])
+AM_CONDITIONAL([HAVE_CXXTESTGEN], [test x"$CXXTESTGEN" != x])
+if test x$CXXTESTGEN != x; then
+ CXXTESTGEN_OPTS="--runner=ErrorPrinter"
+fi
+AC_SUBST(CXXTESTGEN_OPTS)
+
+# Kludge for our binary packaging support
+AC_CHECK_PROGS([DPKG], [dpkg])
+AM_CONDITIONAL([HAVE_DPKG], [test x"$DPKG" != x])
+
+dnl checks for modules
+
+PKG_CHECK_MODULES(GTK, gtk+-2.0,
+ [
+ AC_DEFINE([HAVE_GTK_2], 1,
+ [Define to 1 if you have the gtk+-2.0 library.])
+ ],[
+ PKG_CHECK_MODULES(GTK, gtk+)
+ PKG_CHECK_MODULES(GDK_IMLIB, imlibgdk)
+ ])
+
+AC_ARG_ENABLE(dependency-reduction,
+ AC_HELP_STRING([--enable-dependency-reduction],
+ [removes some unnecessary gtk related dependencies]),
+ [if test "x$enable_dependency_reduction" = "xyes"; then
+ [GTK_LIBS=`echo $GTK_LIBS | sed -e "s|-latk-[0-9.]*||"`
+ GTK_LIBS=`echo $GTK_LIBS | sed -e "s|-lfontconfig||"`
+ GTK_LIBS=`echo $GTK_LIBS | sed -e "s|-lgio-[0-9.]*||"`
+ GTK_LIBS=`echo $GTK_LIBS | sed -e "s|-lgdk_pixbuf-[0-9.]*||"`
+ GTK_LIBS=`echo $GTK_LIBS | sed -e "s|-lgmodule-[0-9.]*||"`
+ GTK_LIBS=`echo $GTK_LIBS | sed -e "s|-lcairo||"`
+ GTK_LIBS=`echo $GTK_LIBS | sed -e "s|-lpango-[0-9.]*||"`
+ GTK_LIBS=`echo $GTK_LIBS | sed -e "s|-lpangocairo-[0-9.]*||"`
+ GTK_LIBS=`echo $GTK_LIBS | sed -e "s|-lfreetype||"`
+ GTK_LIBS=`echo $GTK_LIBS | sed -e "s|-lpangoft2-[0-9.]*||"`
+ GTK_CFLAGS=`echo $GTK_CFLAGS | sed -e "s|-I/usr/include/pixman-[0-9.]*||"`
+ GTK_CFLAGS=`echo $GTK_CFLAGS | sed -e "s|-I/usr/include/freetype2||"`
+ GTK_CFLAGS=`echo $GTK_CFLAGS | sed -e "s|-I/usr/include/directfb||"`
+ GTK_CFLAGS=`echo $GTK_CFLAGS | sed -e "s|-I/usr/include/libpng12||"`]
+ fi])
+
+PKG_CHECK_MODULES(GIMP, gimp-2.0,
+ [
+ AC_DEFINE([HAVE_GIMP_2], 1,
+ [Define to 1 if you have the gimp-2.0 library.])
+ AC_DEFINE([HAVE_ANY_GIMP], 1,
+ [Define to 1 if you have the gimp library.])
+ ],[
+ AC_MSG_CHECKING(for GIMP - version >= 1.0.0)
+ AC_PATH_PROG(GIMP_CONFIG, gimp-config, no)
+ if test x"$GIMP_CONFIG" = xno; then
+ AC_MSG_RESULT(no)
+ else
+ GIMP_CFLAGS=`$GIMP_CONFIG --cflags`
+ AC_MSG_RESULT(yes)
+ AC_DEFINE([HAVE_ANY_GIMP], 1,
+ [Define to 1 if you have the gimp library.])
+ fi
+ ])
+
+AC_ARG_ENABLE(gimp,
+ AC_HELP_STRING([--enable-gimp],
+ [ensure GIMP plugin functionality is available]),
+ [if test "x$enable_gimp" != xno; then
+ if test "x$GIMP_CONFIG" = xno; then
+ AC_MSG_ERROR([required module not found])
+ fi
+ fi])
+
+
+dnl checks for libraries
+
+PKG_CHECK_MODULES(XML, libxml-2.0)
+PKG_CHECK_MODULES(LIBUSB_1_0, libusb-1.0,
+ [
+ AC_DEFINE([HAVE_LIBUSB_1_0], 1,
+ [Define to 1 if you have the libusb-1.0 library.])
+ ],[
+ AC_CHECK_LIB([usb], [usb_get_busses])
+ ])
+AC_CHECK_LIB([sane], [sane_init])
+AC_CHECK_LIB([rt], [clock_gettime], [HAVE_LIBRT=1])
+
+
+dnl checks for header files
+
+AC_USE_SYSTEM_EXTENSIONS
+AC_FUNC_ALLOCA
+AC_HEADER_DIRENT
+AC_HEADER_STDC
+AC_HEADER_SYS_WAIT
+AC_CHECK_HEADERS([\
+ fcntl.h \
+ libintl.h \
+ limits.h \
+ locale.h \
+ sane/sane.h \
+ scsi/sg.h \
+ stddef.h \
+ stdint.h \
+ stdlib.h \
+ string.h \
+ sys/ioctl.h \
+ sys/param.h \
+ sys/time.h \
+ syslog.h \
+ unistd.h \
+ ])
+
+dnl Conditionally check for header files needed to enable GIMP plug-in
+dnl functionality.
+if test x != x"${GTK_CFLAGS}"; then
+ iscan_save_CPPFLAGS="${CPPFLAGS}"
+ CPPFLAGS="${CPPFLAGS} ${GTK_CFLAGS}"
+ AC_CHECK_HEADERS([\
+ gtk/gtk.h \
+ ])
+ CPPFLAGS="${iscan_save_CPPFLAGS}"
+fi
+
+
+dnl checks for typedefs, structures, and compiler characteristics
+
+AC_HEADER_STDBOOL
+AC_C_CONST
+AC_C_INLINE
+AC_TYPE_PID_T
+AC_TYPE_SIZE_T
+AC_STRUCT_TIMEZONE
+AC_HEADER_TIME
+AC_STRUCT_TM
+AC_TYPE_UID_T
+
+
+dnl checks for library functions
+
+AM_GNU_GETTEXT([external])
+AM_GNU_GETTEXT_VERSION(0.14)
+
+AC_FUNC_CLOSEDIR_VOID
+AC_FUNC_FORK
+AC_PROG_GCC_TRADITIONAL
+AC_FUNC_MALLOC
+AC_FUNC_MEMCMP
+AC_FUNC_REALLOC
+AC_FUNC_SELECT_ARGTYPES
+AC_FUNC_STAT
+AC_FUNC_STRTOD
+AC_FUNC_VPRINTF
+AC_CHECK_FUNCS([\
+ alarm \
+ atexit \
+ bzero \
+ floor \
+ memset \
+ regcomp \
+ select \
+ setenv \
+ setlocale \
+ strcasecmp \
+ strncasecmp \
+ strchr \
+ strdup \
+ strerror \
+ strndup \
+ strrchr \
+ strstr \
+ strtol \
+ strtoul \
+ ])
+
+
+dnl The somewhat obscure LT_AC_PROG_SED is here to make sure that the
+dnl SED environment variable gets set before it's used by the libtool
+dnl machinery. This works around buggy libtool versions.
+LT_AC_PROG_SED
+AC_LIBLTDL_INSTALLABLE
+AC_LIBTOOL_DLOPEN
+AC_PROG_LIBTOOL
+AC_LIB_LTDL
+AC_SUBST(LTDLINCL)
+AC_SUBST(LIBLTDL)
+
+
+dnl Graphic file format options
+
+AC_DEFUN([ISCAN_FILE_FORMAT],
+ [AC_ARG_ENABLE($1,
+ AC_HELP_STRING([--enable-$1],
+ [ensure support for the $3 file format]),
+ [if test "x$enable_$1" != xno; then
+ iff_header="`echo $2 | $as_tr_sh`"
+ AC_CHECK_HEADERS([$2])
+ if test `eval echo '$ac_cv_header_'$iff_header` \
+ != yes; then
+ AC_MSG_ERROR([required header file missing])
+ fi
+ fi],
+ [AC_CHECK_HEADERS([$2])])
+ ])
+
+ISCAN_FILE_FORMAT(jpeg,jpeglib.h,JPEG)
+ISCAN_FILE_FORMAT(png,png.h,PNG)
+ISCAN_FILE_FORMAT(tiff,tiffio.h,TIFF)
+
+
+dnl Support for performance measurements.
+
+AC_MSG_CHECKING(whether timing support will be activated)
+AC_ARG_ENABLE(timing,
+ AC_HELP_STRING([--enable-timing],
+ [output crude scan timing statistics]),
+ [if test "x$enable_timing" != xno; then
+ if test "x$HAVE_LIBRT" != x1; then
+ enable_timing=no
+ AC_MSG_WARN([could not find library needed for timing support])
+ fi
+ fi
+ ],
+ [enable_timing=no])
+AC_MSG_RESULT([$enable_timing])
+AM_CONDITIONAL(ENABLE_TIMING, test x$enable_timing = xyes)
+
+
+dnl Facilitate builds on architectures other than i386 and friends.
+
+AC_SUBST(ISCAN_HOST_CPU,[$host_cpu])
+AC_DEFINE_UNQUOTED(ISCAN_HOST_CPU,["$host_cpu"],
+ [Run-time architecture tag])
+AC_MSG_CHECKING(whether to build the frontend application)
+AC_ARG_ENABLE(frontend,
+ AC_HELP_STRING([--enable-frontend],
+ [ensure the frontend application is built]),
+ [if test "x$enable_frontend" != xno; then
+ case $host_cpu in
+ i?86) :
+ ;;
+ x86_64) :
+ ;;
+ *) AC_MSG_ERROR([requires IA32 architecture])
+ ;;
+ esac
+ fi],
+ [case $host_cpu in
+ i?86) enable_frontend=yes ;;
+ x86_64) enable_frontend=yes ;;
+ *) enable_frontend=no ;;
+ esac])
+AC_MSG_RESULT([$enable_frontend])
+AM_CONDITIONAL(ENABLE_FRONTEND, test x$enable_frontend = xyes)
+
+
+dnl SANE related issues
+
+dnl The SANE version information is used by the epkowa backend and in
+dnl the determination of the libtool -version-info.
+AC_SUBST(SANE_MAJOR,1)
+AC_SUBST(SANE_MINOR,0)
+AC_SUBST(SANE_REVISION,15)
+AC_DEFINE_UNQUOTED(SANE_MAJOR, $SANE_MAJOR,
+ [Major version of the SANE API that we claim to support.])
+AC_DEFINE_UNQUOTED(SANE_MINOR, $SANE_MINOR,
+ [Minor version of the SANE API that we claim to support.])
+dnl The code taken from sane-backends provides replacement code for a
+dnl number of functions for reasons of portability.
+AC_REPLACE_FUNCS([\
+ sigprocmask \
+ ])
+
+
+AC_CONFIG_FILES([
+ Makefile
+ backend/Makefile
+ backend/tests/Makefile
+ doc/Makefile
+ frontend/Makefile
+ lib/Makefile
+ lib/pdf/Makefile
+ lib/tests/Makefile
+ non-free/Makefile
+ po/Makefile.in
+ utils/Makefile
+ ])
+
+AC_OUTPUT
diff --git a/doc/Makefile.am b/doc/Makefile.am
new file mode 100644
index 0000000..d29fcc1
--- /dev/null
+++ b/doc/Makefile.am
@@ -0,0 +1,46 @@
+## Makefile.am -- an automake template for a Makefile.in file
+## Copyright (C) 2004 Olaf Meeuwissen
+##
+## This file is part of the "Image Scan!" build infra-structure.
+##
+## The "Image Scan!" build infra-structure is free software.
+## You can redistribute it and/or modify it under the terms of the GNU
+## General Public License as published by the Free Software Foundation;
+## either version 2 of the License or at your option any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+## FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+## See the GNU General Public License for more details.
+##
+## You should have received a verbatim copy of the GNU General Public
+## License along with this program; if not, write to:
+##
+## Free Software Foundation, Inc.
+## 59 Temple Place, Suite 330
+## Boston, MA 02111-1307 USA
+
+man_MANS = \
+ sane-epkowa.5 \
+ iscan-registry.8
+
+if ENABLE_FRONTEND
+man_MANS += iscan.1
+endif
+
+
+EXTRA_DIST = \
+ sane-epkowa.man \
+ iscan.man \
+ iscan-registry.man \
+ xinetd.sane
+
+CLEANFILES = $(man_MANS)
+
+configdir = $(sysconfdir)/sane.d
+
+%.1 %.5 %.8: %.man
+ @sed -e 's|@CONFIGDIR@|$(configdir)|g' \
+ -e 's|@LIBDIR@|$(libdir)/sane|g' \
+ $^ >$@
+ @echo Generating manpage $@...
diff --git a/doc/iscan-registry.man b/doc/iscan-registry.man
new file mode 100644
index 0000000..fc41f06
--- /dev/null
+++ b/doc/iscan-registry.man
@@ -0,0 +1,49 @@
+.TH ISCAN-REGISTRY 8 "2011-04-11" "Image Scan! for Linux" "System Administration Utilities"
+.SH NAME
+iscan-registry \- (un)register iscan interpreter plugins
+.SH SYNOPSIS
+.B iscan-registry
+\fI--help | --version\fR
+.br
+.B iscan-registry
+\fI--add <spec>\fR
+.br
+.B iscan-registry
+\fI--remove <spec>\fR
+.SH DESCRIPTION
+\&'iscan\-registry' updates the run\-time data for Image Scan! for Linux
+.PP
+Image Scan! for Linux plugins may need to (un)register themselves.
+This utility gives plugins a common interface to do just that. You
+should not need to use this utility manually.
+.PP
+The following options are supported:
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+displays this message and exit
+.TP
+\fB\-v\fR, \fB\-\-version\fR
+displays program version and exit
+.TP
+\fB\-a\fR, \fB\-\-add\fR
+adds a plugin to the run\-time data
+.TP
+\fB\-r\fR, \fB\-\-remove\fR
+removes a plugin from the run\-time data
+.PP
+The following <spec>s are supported:
+.IP
+interpreter usb <vendor\-id> <product\-id> <plugin> [<firmware>]
+.PP
+The <vendor\-id> and <product\-id> are the USB IDs in hexadecimal
+notation, prefixed with '0x'. <plugin> and optional <firmware>
+are pathnames to the corresponding files.
+Note that the <plugin> pathname should not include an extension.
+.SH AUTHOR
+Written by AVASYS CORPORATION.
+.SH COPYRIGHT
+Copyright \(co 2008 SEIKO EPSON CORPORATION
+.br
+This is free software. You may redistribute copies of it under the
+terms of the GNU General Public License, version 2 or later.
+See <http://www.gnu.org/licenses/gpl.html> for details.
diff --git a/doc/iscan.man b/doc/iscan.man
new file mode 100644
index 0000000..10505b1
--- /dev/null
+++ b/doc/iscan.man
@@ -0,0 +1,89 @@
+.TH ISCAN 1 "2011-10-19" "Image Scan! for Linux" "User Commands"
+.IX iscan
+.SH NAME
+iscan - Image Scan! for Linux SANE frontend
+.SH DESCRIPTION
+.B Image Scan! for Linux
+(iscan) provides a graphical user-interface to control
+EPSON scanners. It allows the previewing and scanning of images.
+.B iscan
+can be invoked either from the command-line or
+through the GIMP image manipulation program.
+
+When run from the command line,
+.B iscan
+acts as a stand-alone program that saves acquired images in PNM, PNG,
+JPEG, TIFF, PCX or PDF format. Alternatively, acquired images can be sent
+directly to a printer, provided your print system handles PNG natively.
+CUPS and Photo Image Print System, versions 1.3.1 and later, do this.
+LPRng and other LPD based printer systems may need a little help.
+Refer to your print system's documentation for more information on how
+to set this up. When run as a GIMP plugin, the images are passed to
+the GIMP for further processing.
+
+.B iscan
+accesses EPSON image acquisition devices through the SANE (Scanner
+Access Now Easy) interface.
+
+.SH RUNNING UNDER THE GIMP
+As of version 1.15.0,
+.B iscan
+is automatically registered as a
+.BR gimp (1)
+plugin if you install the binary package. In case it didn't (because
+you built from source for example), you can register it yourself by
+creating a symbolic link from the
+.B iscan
+binary to one of the
+.BR gimp (1)
+plug-ins directories. For example, for gimp-1.2.x the command
+.PP
+.RS
+ln -s /usr/bin/iscan ~/.gimp-1.2/plug-ins/
+.RE
+.PP
+adds a symlink for the
+.B iscan
+binary to the user's plug-ins directory. Your system administrator
+can register it for all users by creating a symbolic link in the
+.BR gimp (1)
+system plug-ins directory. After creating such a symlink,
+.B iscan
+will be queried by
+.BR gimp (1)
+the next time it's invoked. From then on,
+.B iscan
+can be invoked through "Xtns->Acquire Image->Scanning (iscan)" menu
+entry.
+.SH PRINTER SETUP
+When not using the default printer , it is necessary to input the
+printer name from /etc/printcap into the Print Command field of the
+Configuration dialog. For example, if the printer name is pm900c,
+input the following command.
+.PP
+.RS
+lpr -Ppm900c
+.SH EPSON Scan! for Linux WORKFLOW
+Use the following steps to scan an image with iscan.
+.PP
+.RS
+.br
+1. Select the document source.
+.br
+2. Select the image type.
+.br
+3. Preview the full page.
+.br
+4. Create a marquee (frame) of the image area to scan.
+.br
+5. Auto expose the selected area.
+.br
+6. Select the image destination.
+.br
+7. Scan the final image.
+.RE
+.SH SEE ALSO
+gimp(1), gimptool(1), scanimage(1), sane-scsi(5), sane\-dll(5),
+sane\-net(5), sane\-"backendname"(5)
+.SH AUTHOR
+Noriyoshi Sasaki and Peter Schretlen
diff --git a/doc/sane-epkowa.man b/doc/sane-epkowa.man
new file mode 100644
index 0000000..dcfa19c
--- /dev/null
+++ b/doc/sane-epkowa.man
@@ -0,0 +1,225 @@
+.TH SANE\-EPKOWA 5 "2012-03-05" "Image Scan! for Linux"
+.IX sane\-epkowa
+.SH NAME
+sane\-epkowa - SANE backend for EPSON scanners
+.SH DESCRIPTION
+The
+.B sane\-epkowa
+library implements a SANE (Scanner Access Now Easy) backend that
+provides access to a large number of EPSON devices. A list with
+supported device information may be found in the
+.I iscan-data
+package's documentation directory, typically
+.I /usr/share/doc/iscan-data/.
+
+.SH OPTIONS
+The options the backend supports can either be selected through command line
+options to programs like scanimage or through GUI elements in xscanimage or xsane.
+
+Valid command line options and their syntax can be listed by using
+.RS
+scanimage \-\-help \-d epkowa
+.RE
+Not all devices support all options and not all options may be docmented below.
+.TP
+.I Scan Mode
+The
+.I \-\-mode
+switch selects the basic mode of operation of the scanner valid choices are
+.I Binary, Gray and Color.
+The Binary mode is black and white only, Gray will produce up to 256 levels of gray
+and Color means 24 bit color mode. Some scanners will internally use 36 bit color,
+the external interface however does only support 24 bits.
+
+The
+.I \-\-halftoning
+switch selects the mode that is used in Binary mode. Valid options are None,
+Halftone A (Hard Tone), Halftone B (Soft Tone), Halftone C (Net Screen),
+Dither A (4x4 Bayer), Dither B (4x4 Spiral), Dither C (4x4 Net Screen),
+Dither D (8x4 Net Screen), Text Enhanced Technology, Download pattern A,
+and Download pattern B.
+
+The
+.I \-\-dropout
+switch selects the so called dropout color. Valid options are None, Red, Green
+and Blue. The default is None. The dropout color is used for
+monochrome scanning and selects the color that is not scanned. This can be used
+to e.g. scan an original with a colored background.
+
+The
+.I \-\-brightness
+switch controls the brightness of the scan. Valid options are the numbers from \-3 to
+3. The default is 0. The larger the brightness value, the brighter the image gets. If
+a user defined table for the gamma correction is selected, the brightness parameter is
+not available.
+
+The
+.I \-\-sharpness
+switch sets the sharpness of the image data. Valid options are the numbers from \-2 to
+2, with \-2 meaning "Defocus", \-1 "Defocus slightly", 0 "Normal", 1 "Sharpen slighly"
+and 2 "Sharpen".
+
+The
+.I \-\-gamma\-correction
+switch controls the scanner's internal gamma correction. Valid options are "Default", "User
+defined", "High density printing" "Low density printing" and "High contrast printing".
+
+The
+.I \-\-color\-correction
+switch controls the scanner's internal color correction function. Valid options are "No
+Correction", "Impact\-dot printers", "Thermal printers", "Ink\-jet printers" and "CRT
+monitors". The default is "CRT monitors".
+
+The
+.I \-\-resolution
+switch selects the resolution for a scan. Many EPSON scanners will scan in any resolution between
+the lowest and highest possible value. The list reported by the scanner can be displayed using
+the "\-\-help \-d epkowa" parameters to scanimage.
+
+The
+.I \-\-mirror
+option controls the way the image is scanned. By reading the image data from right to left the
+image is mirored. Valid options are "yes" and "no". The default is "no".
+
+The
+.I \-\-speed
+option can improve the scan speed in monochrome mode. Valid options are "yes" or "no", the "yes"
+option will speed up the scan if this option is supported.
+
+The
+.I \-\-auto\-area\-segmentation
+switch turns on the automatic area segmentation for monochrome scans. The scanner will try to
+determine which areas are text and which contain images. The image areas will be halftoned,
+and the text will be impoved. Valid options are "yes" and "no". The default is "yes".
+
+The
+.I \-\-gamma\-table
+parameter can be used to download a user defined gamma table. The options takes 256 values from
+the range 0..255. In color mode this option equally affects the red, green, and blue channel.
+
+The
+.I \-\-red\-gamma\-table
+parameter can be used to download a user defined gamma table for the red channel. The valid
+options are the same as for \-\-gamma\-table.
+
+The
+.I \-\-green\-gamma\-table
+parameter can be used to download a user defined gamma table for the green channel. The valid
+options are the same as for \-\-gamma\-table.
+
+The
+.I \-\-blue\-gamma\-table
+parameter can be used to download a user defined gamma table for the blue channel. The valid
+options are the same as for \-\-gamma\-table.
+
+The color correction coefficients
+.I \-\-cct\-1 \-\-cct\-2 \-\-cct\-3 ... \-\-cct\-9
+will install color correction coefficients for the user defined color correction. Possible
+values are in the range \-127..127.
+
+
+The
+.I \-\-preview
+option requests a preview scan. The frontend software automatically selects a low
+resolution. Valid options are "yes" and "no". The default is "no".
+
+The
+.I \-\-preview\-speed
+options will increase the scan speed if this is supported by the scanner. Valid options
+are "yes" and "no", the default is "no".
+
+
+The geometry options
+.I \-l \-t \-x \-y
+control the scan area: \-l sets the top left x coordinate, \-t the top left y coordinate,
+\-x selects the width and \-y the height of the scan aea. All parameters are specified in
+milimeters.
+
+The
+.I \-\-quick\-format
+option lets the user select a scan area with predefined sizes. Valid parameters are "CD",
+"A5 portrait", "A5 landscape", "Letter", "A4" and "max". The default is "max", which
+selects the largest possible area.
+
+The
+.I \-\-source
+option selects the scan source. Valid options depend on the installed options. The default
+is "Flatbed".
+
+The
+.I \-\-auto\-eject
+option will eject a page after scanning from the document feeder.
+
+
+.SH CONFIGURATION FILE
+The configuration file @CONFIGDIR@/epkowa.conf specifies the device(s) that the
+backend will use. Possible connection types are:
+.TP
+.I USB
+All supported USB devices should be recognised automatically when the
+"usb" keyword is specified. You may have to add yourself to a
+specific system group though to get the privileges required to access
+the device. See the configuration file for information on how to get
+unsupported devices recognised.
+.TP
+.I SCSI
+The "scsi" keyword activates detection of supported SCSI devices. If
+your device is not recognised despite this see the configuration file
+for more information.
+.TP
+More extensive documentation is available in the comments of the
+default configuration file.
+.SH FILES
+.TP
+.I @LIBDIR@/libsane\-epkowa.a
+The static library implementing this backend.
+.TP
+.I @LIBDIR@/libsane\-epkowa.so
+The shared library implementing this backend (present on systems that
+support dynamic loading).
+.SH ENVIRONMENT
+.TP
+.B SANE_DEBUG_EPKOWA
+If the library was compiled with debug support enabled, this
+environment variable controls the debug level for this backend. The
+following, case insensitive string literals are supported (in
+increasing level of feedback): FATAL, MAJOR, MINOR, INFO, CALL, DATA,
+CMD, HEX, IMG. Note that the last level includes the raw image data
+as obtained from the scanner and produces vast amounts of output. For
+most trouble shooting situations, the HEX level is sufficient.
+
+.TP
+.B SANE_EPSON_CMD_LVL
+This allows to override the function or command level that the backend
+uses to communicate with the scanner. The function level a scanner
+supports is determined during the initialization of the device. If the
+backend does not recognize the function level reported by the scanner
+it will default to function level B5. Valid function levels include
+A1, A2, B1, B2, B3, B4, B5, B6, B7, B8, D1, D2, D7, D8 and F5. Use
+this feature only if you know what you are doing!
+
+.SH "SEE ALSO"
+
+sane\-scsi(5), scanimage(1), xscanimage(1), xsane(1)
+
+.SH BUGS
+Sometimes the scanner is not initialized correctly. The problem can be
+resolved by killing the program and restarting it again.
+
+.SH UNSUPPORTED DEVICES
+Note that a number of EPSON scanners require a non-free plugin before
+they can be used by the backend.
+
+The backend may be used with EPSON scanners that are not yet listed
+under the list of supported devices. A scanner that is not recognized
+may default to the function level B3, which means that not all
+functions that the scanner may be capable of are accessible.
+
+If the scanner is not even recognized as an EPSON scanner it is
+probably because the device info reported by the scanner is not in
+the correct format. Please send this information to the sane-devel
+mailing list.
+
+.SH AUTHOR
+
+AVASYS CORPORATION
diff --git a/doc/xinetd.sane b/doc/xinetd.sane
new file mode 100644
index 0000000..f3af497
--- /dev/null
+++ b/doc/xinetd.sane
@@ -0,0 +1,15 @@
+# default: off
+# description: SANE network scanner daemon
+# Please note that this service is registered by the IANA under
+# the name "sane-port". If your /etc/services file only has a
+# sane-port entry, rename the service in this file to match it.
+# Alternatively, you can add an alias in /etc/services.
+service sane
+{
+ disabled = no
+ port = 6566
+ socket_type = stream
+ wait = no
+ user = root
+ server = /usr/sbin/saned
+}
diff --git a/frontend/Makefile.am b/frontend/Makefile.am
new file mode 100644
index 0000000..6d7c132
--- /dev/null
+++ b/frontend/Makefile.am
@@ -0,0 +1,102 @@
+## Makefile.am -- an automake template for a Makefile.in file
+## Copyright (C) 2004, 2005, 2007, 2008 Olaf Meeuwissen
+##
+## This file is part of the "Image Scan!" build infra-structure.
+##
+## The "Image Scan!" build infra-structure is free software.
+## You can redistribute it and/or modify it under the terms of the GNU
+## General Public License as published by the Free Software Foundation;
+## either version 2 of the License or at your option any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+## FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+## See the GNU General Public License for more details.
+##
+## You should have received a verbatim copy of the GNU General Public
+## License along with this program; if not, write to:
+##
+## Free Software Foundation, Inc.
+## 59 Temple Place, Suite 330
+## Boston, MA 02111-1307 USA
+
+if ENABLE_FRONTEND
+bin_PROGRAMS = iscan
+iscan_CPPFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/lib \
+ -I$(top_srcdir)/non-free \
+ -DLOCALEDIR=\"$(datadir)/locale\"
+iscan_CXXFLAGS = \
+ @GTK_CFLAGS@ \
+ @GIMP_CFLAGS@ \
+ @GDK_IMLIB_CFLAGS@
+iscan_LDADD = \
+ $(top_builddir)/lib/libimage-stream.la \
+ -lsane \
+ @LIBLTDL@ \
+ @GTK_LIBS@ \
+ @GDK_IMLIB_LIBS@ \
+ $(top_builddir)/non-free/libesmod.so
+iscan_SOURCES = \
+ $(iscan_source_files)
+endif
+
+iscan_source_files = \
+ esmod-wrapper.hh \
+ file-selector.cc \
+ file-selector.h \
+ gimp-plugin.h \
+ pisa_aleart_dialog.cc \
+ pisa_aleart_dialog.h \
+ pisa_change_unit.cc \
+ pisa_change_unit.h \
+ pisa_configuration.cc \
+ pisa_configuration.h \
+ pisa_default_val.h \
+ pisa_enums.h \
+ pisa_error.cc \
+ pisa_error.h \
+ pisa_esmod_structs.h \
+ pisa_gamma_correction.cc \
+ pisa_gamma_correction.h \
+ pisa_gimp.cc \
+ pisa_gimp.h \
+ pisa_gimp_1_0_patch.h \
+ pisa_image_controls.cc \
+ pisa_image_controls.h \
+ pisa_img_converter.cc \
+ pisa_img_converter.h \
+ pisa_main.cc \
+ pisa_main.h \
+ pisa_main_window.cc \
+ pisa_main_window.h \
+ pisa_marquee.cc \
+ pisa_marquee.h \
+ pisa_preference.cc \
+ pisa_preference.h \
+ pisa_preview_window.cc \
+ pisa_preview_window.h \
+ pisa_progress_window.cc \
+ pisa_progress_window.h \
+ pisa_sane_scan.cc \
+ pisa_sane_scan.h \
+ pisa_scan_manager.cc \
+ pisa_scan_manager.h \
+ pisa_scan_selector.cc \
+ pisa_scan_selector.h \
+ pisa_scan_tool.cc \
+ pisa_scan_tool.h \
+ pisa_settings.cc \
+ pisa_settings.h \
+ pisa_structs.h \
+ pisa_tool.cc \
+ pisa_tool.h \
+ pisa_view_manager.cc \
+ pisa_view_manager.h \
+ xpm_data.cc \
+ xpm_data.h
+
+
+EXTRA_DIST = \
+ $(iscan_source_files)
diff --git a/frontend/esmod-wrapper.hh b/frontend/esmod-wrapper.hh
new file mode 100644
index 0000000..b15ed3a
--- /dev/null
+++ b/frontend/esmod-wrapper.hh
@@ -0,0 +1,282 @@
+// esmod-wrapper.hh --
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+#ifndef esmod_wrapper_hh_included
+#define esmod_wrapper_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#include "esmod.hh"
+
+#define ISCAN_DEFAULT_GAMMA ESMOD_DEFAULT_GAMMA
+#define ISCAN_DEFAULT_HILITE ESMOD_DEFAULT_HILITE
+#define ISCAN_DEFAULT_SHADOW ESMOD_DEFAULT_SHADOW
+#define ISCAN_DEFAULT_THRESHOLD ESMOD_DEFAULT_THRESHOLD
+
+#include "pisa_enums.h"
+#include "pisa_structs.h"
+#include "pisa_esmod_structs.h"
+#include "pisa_marquee.h"
+#include "pisa_settings.h"
+
+namespace iscan
+{
+
+ class focus : public esmod::focus
+ {
+ public:
+ focus (const pisa_image_info& parms);
+ focus (struct sharp_img_info parms);
+ };
+
+ class moire : public esmod::moire
+ {
+ public:
+ moire (struct moire_img_info parms, bool is_dumb);
+ };
+
+ class scale : public esmod::scale
+ {
+ public:
+ scale (struct resize_img_info parms);
+ };
+
+ // WARNING: These quite likely modify global state in libesmod.
+ void auto_expose (int, int, const pisa_image_info&, const _rectL&,
+ marquee&, bool, bool is_dumb);
+ void build_LUT (int, int, const settings&, marquee&, bool is_dumb);
+
+ esmod::type_type esmod_film_type (int iscan_film_type);
+ esmod::type_type esmod_focus_type (int iscan_focus_type);
+ esmod::type_type esmod_image_type (int iscan_image_type);
+ esmod::type_type esmod_option_type (int iscan_option_type);
+ esmod::type_type esmod_pixel_type (int iscan_pixel_type);
+ esmod::type_type esmod_scale_type (int iscan_scale_type);
+
+} // namespace iscan
+
+inline
+iscan::focus::focus (const pisa_image_info& info)
+ : esmod::focus::focus (info.m_width, info.m_height, info.m_rowbytes,
+ info.m_bits_per_pixel)
+{
+}
+
+inline
+iscan::focus::focus (const struct sharp_img_info info)
+ : esmod::focus::focus (info.in_width, info.in_height, info.in_rowbytes,
+ info.out_width, info.out_height, info.out_rowbytes,
+ info.bits_per_pixel,
+ info.strength, info.radius, info.clipping,
+ esmod_focus_type (info.sharp_flag))
+{
+}
+
+inline
+iscan::moire::moire (const struct moire_img_info info, bool is_dumb)
+ : esmod::moire::moire (info.in_width, info.in_height, info.in_rowbytes,
+ info.out_width, info.out_height, info.out_rowbytes,
+ info.bits_per_pixel,
+ info.resolution, is_dumb)
+{
+}
+
+inline
+iscan::scale::scale (const struct resize_img_info info)
+ : esmod::scale::scale (info.in_width, info.in_height, info.in_rowbytes,
+ info.out_width, info.out_height, info.out_rowbytes,
+ info.bits_per_pixel,
+ esmod_scale_type (info.resize_flag))
+{
+}
+
+inline void
+iscan::auto_expose (int option_type, int film_type,
+ const pisa_image_info& info, const _rectL& r,
+ marquee& m, bool is_doc, bool is_dumb)
+{
+ esmod::auto_expose (esmod_option_type (option_type),
+ esmod_film_type (film_type), info.m_img,
+ info.m_width, info.m_height, info.m_rowbytes,
+ r.top, r.left, r.bottom, r.right,
+ &m.gamma, &m.highlight, &m.shadow, &m.graybalance,
+ m.film_gamma, m.film_yp, m.grayl,
+ is_doc, is_dumb);
+}
+
+inline void
+iscan::build_LUT (int option_type, int film_type, const settings& s,
+ marquee& m, bool is_dumb)
+{
+ esmod::build_LUT (esmod_option_type (option_type),
+ esmod_film_type (film_type),
+ esmod_pixel_type (s.imgtype.pixeltype),
+ esmod_image_type (s.imgtype.ae_option),
+ m.gamma, m.highlight, m.shadow, m.graybalance,
+ m.film_gamma, m.film_yp, m.grayl,
+ m.gamma_table[0], m.gamma_table[1], m.lut.gamma_r,
+ is_dumb);
+}
+
+inline esmod::type_type
+iscan::esmod_film_type (int iscan_film_type)
+{
+ esmod::type_type val;
+
+ switch (iscan_film_type)
+ {
+ case PISA_FT_POSI:
+ val = ESMOD_FILM_POSITIVE;
+ break;
+ case PISA_FT_NEGA:
+ val = ESMOD_FILM_NEGATIVE;
+ break;
+ case PISA_FT_REFLECT:
+ val = -1;
+ break;
+ default:
+ throw;
+ }
+ return val;
+}
+
+inline esmod::type_type
+iscan::esmod_focus_type (int iscan_focus_type)
+{
+ esmod::type_type val;
+
+ switch (iscan_focus_type)
+ {
+ case PISA_SH_UMASK:
+ val = ESMOD_FOCUS_UMASK;
+ break;
+ case PISA_SH_GAUSS:
+ val = ESMOD_FOCUS_GAUSS;
+ break;
+ case PISA_SH_UMASKY:
+ val = ESMOD_FOCUS_UMASK_Y;
+ break;
+ default:
+ throw;
+ }
+ return val;
+}
+
+inline esmod::type_type
+iscan::esmod_image_type (int iscan_image_type)
+{
+ esmod::type_type val;
+
+ switch (iscan_image_type)
+ {
+ case PISA_AE_PHOTO:
+ val = ESMOD_IMAGE_PHOTO;
+ break;
+ case PISA_AE_DOC:
+ val = ESMOD_IMAGE_DOCUMENT;
+ break;
+ case PISA_AE_GRAYED:
+ val = ESMOD_IMAGE_LINE_ART;
+ break;
+ default:
+ throw;
+ }
+ return val;
+}
+
+inline esmod::type_type
+iscan::esmod_option_type (int iscan_option_type)
+{
+ esmod::type_type val;
+
+ switch (iscan_option_type)
+ {
+ case PISA_OP_FLATBED:
+ val = ESMOD_OPTION_FLATBED;
+ break;
+ case PISA_OP_ADF:
+ case PISA_OP_ADFDPLX:
+ val = ESMOD_OPTION_ADF;
+ break;
+ case PISA_OP_TPU:
+ val = ESMOD_OPTION_TPU;
+ break;
+ default:
+ throw;
+ }
+ return val;
+}
+
+inline esmod::type_type
+iscan::esmod_pixel_type (int iscan_pixel_type)
+{
+ esmod::type_type val;
+
+ switch (iscan_pixel_type)
+ {
+ case PISA_PT_BW:
+ val = ESMOD_PIXEL_MONO;
+ break;
+ case PISA_PT_GRAY:
+ val = ESMOD_PIXEL_GRAY;
+ break;
+ case PISA_PT_RGB:
+ val = ESMOD_PIXEL_RGB;
+ break;
+ default:
+ throw;
+ }
+ return val;
+}
+
+inline esmod::type_type
+iscan::esmod_scale_type (int iscan_scale_type)
+{
+ esmod::type_type val;
+
+ switch (iscan_scale_type)
+ {
+ case PISA_RS_NN:
+ val = ESMOD_SCALE_NEAREST_NEIGHBOUR;
+ break;
+ case PISA_RS_BL:
+ val = ESMOD_SCALE_BILINEAR;
+ break;
+ case PISA_RS_BC:
+ val = ESMOD_SCALE_BICUBIC;
+ break;
+ default:
+ throw;
+ }
+ return val;
+}
+
+#endif /* !defined (esmod_wrapper_hh_included) */
diff --git a/frontend/file-selector.cc b/frontend/file-selector.cc
new file mode 100644
index 0000000..4848168
--- /dev/null
+++ b/frontend/file-selector.cc
@@ -0,0 +1,1137 @@
+/* file-selector.cc -- customized file selection dialog
+ Copyright (C) 2003, 2005, 2008 SEIKO EPSON CORPORATION
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "file-selector.h"
+#include "gettext.h"
+#define _(msg_id) gettext (msg_id)
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+#include "pisa_aleart_dialog.h"
+#include "pisa_enums.h"
+#include "pisa_error.h"
+
+#include "../lib/pcxstream.hh"
+#include "../lib/pnmstream.hh"
+#include "../lib/pngstream.hh"
+#include "../lib/jpegstream.hh"
+#include "../lib/tiffstream.hh"
+#include "../lib/pdfstream.hh"
+
+#ifndef DEBUG
+#define g_print(...)
+#endif
+
+
+#ifdef DONT_HAVE_GTK_2
+#undef HAVE_GTK_2
+#endif
+
+#ifndef HAVE_GTK_2
+#define G_CALLBACK GTK_SIGNAL_FUNC
+#define g_signal_stop_emission_by_name gtk_signal_emit_stop_by_name
+#define g_signal_connect_swapped gtk_signal_connect_object
+#define g_signal_connect gtk_signal_connect
+#define GTK1_OBJ(obj) GTK_OBJECT (obj)
+GtkWidget * // "stolen" from GTK+2.0
+gtk_widget_get_parent (GtkWidget *widget)
+{
+ g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
+
+ return widget->parent;
+}
+#else
+#define GTK1_OBJ(obj) obj
+#endif
+
+struct menu_info // helper struct for callbacks
+{
+ file_selector *fs;
+ GtkWidget *widget[4]; // holding "ADF" spinboxes and labels
+ int index;
+};
+
+
+// callback implementations
+
+static void
+_destroy (file_selector *fs)
+{
+ g_print ("%s\n", __func__);
+
+ fs->destroy (true);
+}
+
+#if 0
+static void
+_click_ng( GtkWidget *, file_selector * )
+{
+ DBG_FS fprintf( stderr, "_click_ng\n" );
+
+ gtk_main_quit();
+}
+#endif
+
+static void
+_click_ok (file_selector *fs)
+{
+ g_print ("%s\n", __func__);
+
+ if (fs->save_pathname ())
+ {
+ fs->destroy ();
+ gtk_main_quit ();
+ }
+}
+
+// FIXME: this really should be using image stream capability API to
+// determine what is and what is not supported.
+static void
+_change_format (GtkWidget *, struct menu_info *id)
+{
+ id->fs->change_format( id->index ); // fs->get_type() below, requires this
+
+ // adjust "file with all pages" check box
+ bool make_active = (iscan::PDF == id->fs->get_type () ||
+ iscan::TIF == id->fs->get_type ());
+ gtk_widget_set_sensitive (id->widget[0], make_active);
+ if (!make_active) // disable multi-page mode when changing to a format that
+ // does not support it, do not do the opposite!
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (id->widget[0]), false);
+ }
+
+ // adjust "binding edge" selector sensitivity
+ // We only support this for file formats (i.e. `imgstream` classes)
+ // that support "easy" rotation. Yes, this is utterly flawed. The
+ // "binding edge" selector _should_ be in the main part of the GUI.
+ // It has no dependency on saving to file whatsoever, it is an issue
+ // that depends on device capabilities and document characteristics
+ // only.
+ make_active = (iscan::PDF == id->fs->get_type ());
+ gtk_widget_set_sensitive (id->widget[1], make_active);
+ gtk_widget_set_sensitive (id->widget[2], make_active);
+ gtk_widget_set_sensitive (id->widget[3], make_active);
+}
+
+static void
+_delete_text (GtkEditable *ed, gint start, gint end, file_selector *fs)
+{
+ g_print ("%s\n", __func__);
+
+#ifndef HAVE_GTK_2
+ gtk_signal_handler_block_by_func (GTK_OBJECT (ed),
+ GTK_SIGNAL_FUNC (_delete_text), fs);
+ fs->delete_text (start, end);
+ gtk_signal_handler_unblock_by_func (GTK_OBJECT (ed),
+ GTK_SIGNAL_FUNC (_delete_text), fs);
+#else
+ g_signal_handlers_block_by_func (ed, (gpointer) _delete_text, fs);
+ fs->delete_text (start, end);
+ g_signal_handlers_unblock_by_func (ed, (gpointer) _delete_text, fs);
+#endif
+
+ g_signal_stop_emission_by_name (GTK1_OBJ(ed), "delete-text");
+}
+
+static void
+_insert_text (GtkEditable *ed, gchar *text, gint len, gint *pos,
+ file_selector *fs)
+{
+ g_print ("%s\n", __func__);
+
+#ifndef HAVE_GTK_2
+ gtk_signal_handler_block_by_func (GTK_OBJECT (ed),
+ GTK_SIGNAL_FUNC ( _insert_text), fs);
+ fs->insert_text (text, len, pos);
+ gtk_signal_handler_unblock_by_func (GTK_OBJECT (ed),
+ GTK_SIGNAL_FUNC (_insert_text), fs);
+#else
+ g_signal_handlers_block_by_func (ed, (gpointer) _insert_text, fs);
+ fs->insert_text (text, len, pos);
+ g_signal_handlers_unblock_by_func (ed, (gpointer) _insert_text, fs);
+#endif
+
+ g_signal_stop_emission_by_name (GTK1_OBJ(ed), "insert-text");
+}
+
+static void
+_change_seqnum (GtkAdjustment *, file_selector *fs)
+{
+ fs->change_seqnum ();
+}
+
+static void
+_change_digits (GtkAdjustment *, file_selector *fs)
+{
+ fs->change_digits ();
+}
+
+static void
+_toggled_multi_page_mode_fs (GtkWidget *widget, file_selector *fs)
+{
+ fs->change_page_mode (gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (widget)));
+}
+
+static void
+_changed_binding_edge (GtkWidget *widget, file_selector *fs)
+{
+ fs->change_binding_edge (gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (widget)));
+}
+static void
+_toggled_multi_page_mode_widget (GtkWidget *widget, GtkWidget *p)
+{
+ bool on = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
+ gtk_widget_set_sensitive (p, !on);
+}
+
+// class implementation
+
+using std::string;
+using std::stringstream;
+using iscan::file_opener;
+
+ // define static variables
+const file_selector::format
+file_selector::_format[] = {
+ { "PCX", 0, "pcx", "pcx", iscan::PCX },
+ { "PNM", 0, "pnm", "pnm", iscan::PNM },
+ { "PNG", 0, "png", "png", iscan::PNG },
+ { "JPEG", 0, "jpg", "jpg|jpeg", iscan::JPG },
+ { "TIFF", 0, "tiff", "tif|tiff", iscan::TIF },
+ { "PDF", 0, "pdf", "pdf", iscan::PDF },
+ { 0, 0, 0, 0, iscan::PNM } // array terminator
+};
+
+void
+file_selector::init ()
+{
+ g_print ("%s\n", __func__);
+
+ _widget = NULL;
+ _filename = NULL;
+ _pathname = NULL;
+
+ _using_adf = false;
+ _format_index = 0;
+ _deleted_all = false;
+ _multi_page_mode = false;
+ _has_left_edge_binding = true;
+
+ _number = -1;
+ _seqnum = NULL;
+ _digits = NULL;
+
+ int size = 1; // terminating { 0 } element
+ for (int i = 0; 0 != _format[i].name; ++i)
+ ++size;
+ _fmt = new const format * [size];
+ // okay, so we may waste a few
+
+ // Looks like the idea is to only add supported formats to _fmt and
+ // leave unsupported formats out.
+ int cnt = 0;
+ int len = 0;
+ for (int i = 0; i < size - 1; ++i)
+ {
+ _fmt[cnt] = &_format[i]; // FIXME: check support
+ len += strlen (_fmt[cnt]->rgx);
+ ++len; // for the '|'
+ ++cnt;
+ }
+ _fmt[cnt] = 0; // array terminator
+
+ _ext_regex = new char[len]; // overwrite last '|' with '\0'
+ char *pos = _ext_regex;
+ for (int i = 0; _fmt[i]; ++i)
+ {
+ strcpy (pos, _fmt[i]->rgx);
+ pos += strlen (_fmt[i]->rgx);
+ *pos++ = '|'; // overwrite '\0', then advance
+ }
+ *--pos = '\0'; // go back one, then terminate
+
+ g_print ("%s: ext_regex =`%s'\n", __func__, _ext_regex);
+}
+
+void
+file_selector::create_window (GtkWindow *window, GtkWidget *parent,
+ bool do_consecutive_scanning,
+ bool do_duplex,
+ const char* default_format)
+{
+ g_print ("%s: enter\n", __func__);
+
+ _parent = parent;
+ _using_adf = do_consecutive_scanning;
+ _do_duplex = do_duplex;
+
+ _widget = GTK_FILE_SELECTION (gtk_file_selection_new (PACKAGE));
+
+ if (!_widget)
+ throw pisa_error( PISA_STATUS_NO_MEM );
+
+ gtk_window_set_transient_for (GTK_WINDOW (_widget), window);
+ gtk_file_selection_hide_fileop_buttons (_widget);
+
+ gint index = 0;
+ if (is_supported (default_format))
+ index = get_index (default_format);
+ else if (is_supported ("PNG"))
+ index = get_index ("PNG");
+
+ add_dialog_extensions (index);
+ // needs to be called before we canonize
+
+ if (!_filename)
+ _filename = canonize ("default");
+
+ free (_pathname);
+ _pathname = 0;
+ // FIXME? 0 == _filename
+ gtk_file_selection_set_filename (_widget, _filename);
+
+ g_signal_connect_swapped (GTK1_OBJ (_widget->ok_button), "clicked",
+ G_CALLBACK (_click_ok), GTK1_OBJ (this));
+ g_signal_connect_swapped (GTK1_OBJ (_widget->cancel_button), "clicked",
+ G_CALLBACK (_destroy), GTK1_OBJ (this));
+ g_signal_connect_swapped (GTK1_OBJ (_widget), "delete_event",
+ G_CALLBACK (_destroy), GTK1_OBJ (this));
+ g_signal_connect_swapped (GTK1_OBJ (_widget), "destroy",
+ G_CALLBACK (gtk_main_quit), GTK1_OBJ (_widget));
+
+ // We need to interfere with users selecting and editing filenames
+ // to guarantee a correct extension and the appropriate templating
+ // bit when _using_adf.
+ g_signal_connect (GTK1_OBJ (_widget->selection_entry), "delete-text",
+ G_CALLBACK (_delete_text), this);
+ g_signal_connect (GTK1_OBJ (_widget->selection_entry), "insert-text",
+ G_CALLBACK (_insert_text), this);
+
+ gtk_widget_show (GTK_WIDGET (_widget));
+ gtk_grab_add (GTK_WIDGET (_widget));
+ gtk_main ();
+ if (_widget)
+ {
+ gtk_grab_remove (GTK_WIDGET (_widget));
+ }
+ g_print ("%s: exit\n", __func__);
+}
+
+void
+file_selector::destroy ()
+{
+ g_print ("%s\n", __func__);
+
+ if (_using_adf)
+ {
+ hide ();
+ }
+ else
+ {
+ destroy (true);
+ }
+}
+
+void
+file_selector::destroy (bool really)
+{
+ g_print ("%s (%i)\n", __func__, really);
+
+ free (_filename);
+ _filename = NULL;
+
+ if (_widget)
+ {
+ gtk_widget_destroy (GTK_WIDGET (_widget));
+ }
+ _widget = NULL;
+
+ if (_parent)
+ {
+ gtk_widget_set_sensitive (_parent, true);
+ }
+ _parent = NULL;
+
+ _number = -1;
+ _seqnum = NULL; // gtk_widget_destroy cleans these up
+ _digits = NULL;
+
+ _using_adf = false;
+ _do_duplex = false;
+ _deleted_all = false;
+
+ delete [] _fmt;
+ _fmt = NULL;
+ delete [] _ext_regex;
+ _ext_regex = NULL;
+}
+
+char *
+file_selector::get_pathname () const
+{
+ return (_pathname ? strdup (_pathname) : NULL);
+}
+
+int
+file_selector::get_sequence_number () const
+{
+ return _number;
+}
+
+bool
+file_selector::valid_pathname (const string& path) const
+{
+ string::size_type slash = path.rfind (file_opener::dir_sep);
+ string name = path.substr (slash + 1);
+ string::size_type dot = name.rfind (file_opener::ext_sep);
+
+ string::size_type cmp_pos = dot;
+
+ if (_using_adf && !_multi_page_mode)
+ {
+ string::size_type dash = name.find_last_of ("-", dot - 1);
+ cmp_pos = dash;
+ }
+
+ if (128 < path.size () // blame the spec for this one
+ || !permission (path.c_str ())
+ || 0 >= cmp_pos) // 0 length filename
+ {
+ return false;
+ }
+
+ return true;
+}
+
+bool
+file_selector::save_pathname()
+{
+ g_print ("%s\n", __func__);
+
+ if (!_widget)
+ {
+ g_print ("%s: widget's gone!\n", __func__);
+ free (_pathname);
+ _pathname = NULL;
+ return true;
+ }
+
+ const char *current = gtk_file_selection_get_filename (_widget);
+
+ if (!valid_pathname (current))
+ {
+ show_message (pisa_error (PISA_ERR_FILENAME));
+ return false;
+ }
+ if (possible_overwrite (current))
+ {
+ bool ok = show_message (pisa_error (PISA_ERR_OVERWRITE),
+ _("Overwrite"), _("Cancel"));
+ if (!ok)
+ return false;
+ }
+
+ char *new_name = (char *) malloc ((strlen (current) + 1)
+ * sizeof (char));
+ if (!new_name)
+ throw pisa_error (PISA_STATUS_NO_MEM);
+
+ strcpy (new_name, current);
+ free (_pathname);
+ _pathname = new_name;
+ free (_filename);
+ _filename = 0;
+
+ g_print ("%s: pathname = `%s'\n", __func__, _pathname);
+
+ return true;
+}
+
+void
+file_selector::hide () const
+{
+ g_print ("%s\n", __func__);
+
+ if (_using_adf)
+ {
+ g_print ("%s: calling gtk_widget_hide\n", __func__);
+ if (_widget)
+ {
+ gtk_widget_hide (GTK_WIDGET (_widget));
+ }
+ if (_parent)
+ {
+ gtk_widget_set_sensitive (_parent, true);
+ }
+ }
+}
+
+void
+file_selector::set_entry (const char *text)
+{
+ g_print ("%s (%s)\n", __func__, text);
+
+ if (!_filename)
+ return; // logical error but return for now
+
+ if (!text
+ || !*text // empty string
+ || text == _filename
+ || 0 == strcmp (text, _filename))
+ return; // nothing to change
+
+ char *new_name = (canonize (text));
+ if (new_name)
+ {
+ free (_filename);
+ _filename = new_name;
+#ifndef HAVE_GTK_2
+ gtk_signal_handler_block_by_func (GTK_OBJECT (_widget->selection_entry),
+ GTK_SIGNAL_FUNC (_delete_text), this);
+ gtk_signal_handler_block_by_func (GTK_OBJECT (_widget->selection_entry),
+ GTK_SIGNAL_FUNC (_insert_text), this);
+ gtk_entry_set_text (GTK_ENTRY (_widget->selection_entry), _filename);
+ gtk_signal_handler_unblock_by_func (GTK_OBJECT (_widget->selection_entry),
+ GTK_SIGNAL_FUNC (_insert_text), this);
+ gtk_signal_handler_unblock_by_func (GTK_OBJECT (_widget->selection_entry),
+ GTK_SIGNAL_FUNC (_delete_text), this);
+#else
+ g_signal_handlers_block_by_func (GTK_EDITABLE (_widget->selection_entry),
+ (gpointer) _delete_text, this);
+ g_signal_handlers_block_by_func (GTK_EDITABLE (_widget->selection_entry),
+ (gpointer) _insert_text, this);
+ gtk_entry_set_text (GTK_ENTRY (_widget->selection_entry), _filename);
+ g_signal_handlers_unblock_by_func (GTK_EDITABLE (_widget->selection_entry),
+ (gpointer) _insert_text, this);
+ g_signal_handlers_unblock_by_func (GTK_EDITABLE (_widget->selection_entry),
+ (gpointer) _delete_text, this);
+#endif
+ }
+}
+
+void
+file_selector::delete_text (gint start, gint end)
+{
+ g_print ("%s (%d,%d)\n", __func__, start, end);
+ g_print ("%s orig _filename '%s'\n", __func__, _filename);
+
+ _deleted_all = ((end - start) == (gint) strlen (_filename));
+
+ if (0 > end) end = strlen (_filename) + 1;
+ int dot = strrchr (_filename, ((_using_adf && !_multi_page_mode)
+ ? '-' : '.')) - _filename;
+ if (end > dot) end = dot;
+
+ if (start < end)
+ {
+ GtkWidget *ed = _widget->selection_entry;
+ gtk_editable_delete_text (GTK_EDITABLE (ed), start, end);
+ _filename = strdup (gtk_entry_get_text (GTK_ENTRY (ed)));
+
+ g_print ("%s new _filename '%s'\n", __func__, _filename);
+ }
+}
+
+void
+file_selector::insert_text (gchar *text, gint len, gint *pos)
+{
+ g_print ("%s (%s,%d,%d)\n", __func__, text, len, (pos ? *pos : -1));
+ g_print ("%s orig _filename '%s'\n", __func__, _filename);
+
+ if (_deleted_all && pos && (0 == *pos))
+ {
+ _deleted_all = false;
+ text = canonize (text);
+ if (!text) return;
+ len = strlen (text);
+
+ GtkEditable *ed = GTK_EDITABLE (_widget->selection_entry);
+#ifndef HAVE_GTK_2
+ gtk_signal_handler_block_by_func (GTK_OBJECT (ed),
+ GTK_SIGNAL_FUNC (_delete_text), this);
+ gtk_editable_delete_text (ed, 0, -1);
+ gtk_signal_handler_unblock_by_func (GTK_OBJECT (ed),
+ GTK_SIGNAL_FUNC (_delete_text), this);
+#else
+ g_signal_handlers_block_by_func (ed, (gpointer) _delete_text, this);
+ gtk_editable_delete_text (ed, 0, -1);
+ g_signal_handlers_unblock_by_func (ed, (gpointer) _delete_text, this);
+#endif
+ }
+
+ int dot = strrchr (_filename, ((_using_adf && !_multi_page_mode)
+ ? '-' : '.')) - _filename;
+
+ if (pos && (*pos <= dot))
+ {
+ GtkWidget *ed = _widget->selection_entry;
+ gtk_editable_insert_text (GTK_EDITABLE (ed), text, len, pos);
+#ifndef HAVE_GTK_2
+ { // not getting delete-event's for some
+ // reason
+ char *name = canonize (gtk_entry_get_text (GTK_ENTRY (ed)));
+ gtk_entry_set_text (GTK_ENTRY (ed), name);
+ free (name);
+ }
+#endif
+ free (_filename);
+ _filename = strdup (gtk_entry_get_text (GTK_ENTRY (ed)));
+
+ g_print ("%s new _filename '%s'\n", __func__, _filename);
+ }
+}
+
+void
+file_selector::change_format (int index)
+{
+ g_print ("%s (%d)\n", __func__, index);
+
+ _format_index = index;
+ // reflect changes in filename
+ char *canon = canonize (_filename);
+ set_entry (canon);
+ free (canon);
+}
+
+void
+file_selector::change_seqnum ()
+{
+ g_print ("%s\n", __func__);
+
+ _number = int (_seqnum->value);
+}
+
+void
+file_selector::change_digits ()
+{
+ g_print ("%s\n", __func__);
+
+ int max = 9; // there's at least one digit
+ for (int i = 2; i <= int (_digits->value); ++i)
+ {
+ max *= 10;
+ max += 9;
+ }
+
+ if (_seqnum)
+ {
+ _seqnum->upper = max;
+ if (max < _seqnum->value) // also updates the GUI
+ gtk_adjustment_set_value (_seqnum, max);
+
+ gtk_adjustment_changed (_seqnum);
+ }
+
+ if (_filename) // reflect changes in filename
+ {
+ char *canon = canonize (_filename);
+ set_entry (canon);
+ free (canon);
+ }
+}
+
+#include <sstream>
+#include <iostream>
+void
+file_selector::change_page_mode (bool mode)
+{
+ _multi_page_mode = mode;
+
+ string name = _filename;
+ stringstream ss;
+
+ string::size_type dot = name.rfind (file_opener::ext_sep);
+ string::size_type dash = name.find_last_of ("-", dot - 1);
+
+ if (mode)
+ {
+ ss << name.substr (0, dash)
+ << name.substr (dot);
+ }
+ else
+ {
+ ss << name.substr (0, dot) << "-";
+ for (int i = 0; i < int (_digits->value); ++i)
+ ss << file_opener::hash_mark;
+ ss << name.substr (dot);
+ }
+
+ set_entry (ss.str ().c_str ());
+}
+
+void
+file_selector::change_binding_edge (bool is_left_edge)
+{
+ g_print ("%s(%d)\n", __func__, is_left_edge);
+
+ _has_left_edge_binding = is_left_edge;
+}
+
+// caller needs to free returned char *
+char *
+file_selector::canonize (const char *text) const
+{
+ if (!text || 0 == *text)
+ return 0;
+
+ g_print ("%s (%s)\n", __func__, text);
+
+ string name = text;
+ string ext = _fmt[_format_index]->ext;
+ stringstream ss;
+
+ string::size_type dot = name.rfind (file_opener::ext_sep);
+
+ if (_using_adf && !_multi_page_mode)
+ {
+ string::size_type dash = name.find_last_of ("-", dot - 1);
+ ss << name.substr (0, dash)
+ << "-";
+
+ for (int i = 0; i < int (_digits->value); ++i)
+ ss << file_opener::hash_mark;
+ }
+ else
+ {
+ ss << name.substr (0, dot);
+ }
+
+ ss << file_opener::ext_sep
+ << ext;
+
+ return strdup (ss.str ().c_str ());
+}
+
+void
+file_selector::add_dialog_extensions (gint index)
+{
+ g_print ("%s: enter\n", __func__);
+
+ GtkWidget *frame = gtk_frame_new (_("Save Options"));
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
+
+ GtkWidget *align = gtk_alignment_new (1.0, 0.5, 0.0, 0.0);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (align), 5, 5, 5, 5);
+ gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (align));
+ gtk_widget_show (align);
+
+ unsigned short rows = 1;
+ if (_using_adf) rows = 2;
+ if (_do_duplex) rows = 3;
+ GtkTable *opts
+ = (GtkTable *) gtk_table_new (rows + (_using_adf ? 2 : 0), 3, FALSE);
+ gtk_table_set_col_spacings (opts, 5);
+ gtk_container_add (GTK_CONTAINER (align), GTK_WIDGET (opts));
+ gtk_widget_show (GTK_WIDGET (opts));
+
+ GtkWidget *mi = NULL;
+ GtkWidget *multi =
+ gtk_check_button_new_with_label ( _("Create file with all pages"));
+
+ GtkWidget *bind_edge = gtk_label_new
+ (_("Binding Position (for Double-Sided Scanning)"));
+ GtkWidget *left_edge = gtk_radio_button_new_with_label (NULL, _("Left"));
+ GtkWidget *top_edge = gtk_radio_button_new_with_label
+ (gtk_radio_button_get_group (GTK_RADIO_BUTTON (left_edge)), _("Top"));
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (left_edge), true);
+
+ GtkWidget *w = 0;
+ { // file type selector
+ w = gtk_label_new (_("Determine File Type:"));
+ gtk_table_attach_defaults (opts, w, 0, 1, 0, 1);
+ gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
+ gtk_widget_show (w);
+
+ w = gtk_option_menu_new ();
+ {
+ GtkWidget *m = gtk_menu_new ();
+
+ for (int i = 0; _fmt[i]; ++i)
+ {
+ mi = gtk_menu_item_new_with_label (_fmt[i]->name);
+ gtk_menu_append (GTK_MENU (m), mi);
+ gtk_widget_show (mi);
+
+ struct menu_info *id = new struct menu_info;
+ id->fs = this;
+ id->widget[0] = multi;
+ id->widget[1] = bind_edge;
+ id->widget[2] = left_edge;
+ id->widget[3] = top_edge;
+ id->index = i;
+
+ g_signal_connect (GTK1_OBJ (mi), "activate",
+ G_CALLBACK (_change_format), (void *) id);
+
+ gtk_widget_set_sensitive (mi, is_supported (_fmt[i]->name));
+ }
+ gtk_option_menu_set_menu( GTK_OPTION_MENU( w ), m );
+ }
+ gtk_table_attach_defaults (opts, w, 1, 3, 0, 1);
+ gtk_option_menu_set_history (GTK_OPTION_MENU (w), index);
+ change_format (index);
+ gtk_widget_show (w);
+ }
+
+ if (_using_adf)
+ {
+ g_print ("%s: using adf\n", __func__);
+
+ gtk_signal_connect (GTK_OBJECT (multi), "toggled",
+ GTK_SIGNAL_FUNC (_toggled_multi_page_mode_fs), this);
+ gtk_table_attach_defaults (opts, multi, 1, 3, 1, 2);
+ gtk_widget_show (multi);
+
+ if (_do_duplex)
+ {
+ gtk_signal_connect (GTK_OBJECT (left_edge), "toggled",
+ GTK_SIGNAL_FUNC (_changed_binding_edge), this);
+ gtk_table_attach_defaults (opts, bind_edge, 0, 1, 2, 3);
+ gtk_misc_set_alignment (GTK_MISC (bind_edge), 1.0, 0.5);
+ gtk_table_attach_defaults (opts, left_edge, 1, 2, 2, 3);
+ gtk_table_attach_defaults (opts, top_edge, 2, 3, 2, 3);
+ gtk_widget_show (bind_edge);
+ gtk_widget_show (left_edge);
+ gtk_widget_show (top_edge);
+ }
+
+ // sequence number selector
+ w = gtk_label_new (_("Start filing at:"));
+ gtk_table_attach_defaults (opts, w, 0, 1, rows + 0, rows + 1);
+ gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
+ gtk_signal_connect (GTK_OBJECT (multi), "toggled",
+ GTK_SIGNAL_FUNC (_toggled_multi_page_mode_widget), w);
+ gtk_widget_show (w);
+
+ if (!_seqnum)
+ _seqnum = (GtkAdjustment *) gtk_adjustment_new (1, 0, 9, 1, 10, 0);
+ if (_seqnum)
+ _number = int (_seqnum->value);
+ else // play it safe and make sure
+ _number = -1;
+
+ w = gtk_spin_button_new (_seqnum, 0, 0);
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (w), true);
+ gtk_spin_button_set_snap_to_ticks (GTK_SPIN_BUTTON (w), true);
+ gtk_table_attach_defaults (opts, w, 1, 3, rows + 0, rows + 1);
+ gtk_signal_connect (GTK_OBJECT (multi), "toggled",
+ GTK_SIGNAL_FUNC (_toggled_multi_page_mode_widget), w);
+ gtk_widget_show (w);
+
+ // number of digits selector
+ w = gtk_label_new (_("Number of digits:"));
+ gtk_table_attach_defaults (opts, w, 0, 1, rows + 1, rows + 2);
+ gtk_misc_set_alignment (GTK_MISC (w), 1.0, 0.5);
+ gtk_signal_connect (GTK_OBJECT (multi), "toggled",
+ GTK_SIGNAL_FUNC (_toggled_multi_page_mode_widget), w);
+ gtk_widget_show (w);
+
+ if (!_digits)
+ _digits = (GtkAdjustment *) gtk_adjustment_new (3, 1, 6, 1, 1, 0);
+ if (_digits)
+ change_digits();
+
+ w = gtk_spin_button_new (_digits, 0, 0);
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (w), true);
+ gtk_spin_button_set_snap_to_ticks (GTK_SPIN_BUTTON (w), true);
+ gtk_table_attach_defaults (opts, w, 1, 3, rows + 1, rows + 2);
+ gtk_signal_connect (GTK_OBJECT (multi), "toggled",
+ GTK_SIGNAL_FUNC (_toggled_multi_page_mode_widget), w);
+ gtk_widget_show (w);
+
+ g_signal_connect (GTK1_OBJ (_seqnum), "value_changed",
+ G_CALLBACK (_change_seqnum), this);
+ g_signal_connect (GTK1_OBJ (_digits), "value_changed",
+ G_CALLBACK (_change_digits), this);
+ }
+
+ gtk_box_pack_end (GTK_BOX (_widget->main_vbox), frame, false, false, 0);
+
+ struct menu_info id;
+ id.fs = this;
+ id.widget[0] = multi;
+ id.widget[1] = bind_edge;
+ id.widget[2] = left_edge;
+ id.widget[3] = top_edge;
+ id.index = index;
+ _change_format (mi, &id);
+ gtk_widget_show (frame);
+ gtk_widget_show (GTK_WIDGET (opts));
+
+ g_print ("%s: exit\n", __func__);
+}
+
+bool
+file_selector::permission( const char *file_in ) const
+{
+ if (!file_in || 0 == strlen (file_in))
+ return false;
+
+ char* file = strdup (file_in);
+
+ if (!file)
+ return false;
+
+ if (0 == access( file, F_OK )) // file exists
+ {
+ bool rv = (0 == access( file, W_OK )); // whether we can write to it
+ free (file);
+ return rv;
+ }
+
+ // check write access to the directory (note that we need execute
+ // privileges as well)
+
+ char *slash = strrchr( file, '/');
+ *slash = '\0'; // temporarily truncate to dirname
+ const char *dir = (file == slash
+ ? "/" // whoops!, file in root directory
+ : file);
+
+ bool w_ok = false; // assume the worst
+ if (0 == access( dir, F_OK ))
+ w_ok = (0 == access( dir, W_OK | X_OK ));
+
+ *slash = '/'; // restore filename
+
+ free (file);
+ return w_ok;
+}
+
+bool
+file_selector::show_message( const pisa_error& oops,
+ const char *yes,
+ const char *no ) const
+{
+ aleart_dialog dlg;
+
+ if (yes && no) // binary question
+ return (1 == dlg.message_box( GTK_WIDGET( _widget ),
+ oops.get_error_string(),
+ yes, no ));
+
+ dlg.message_box( GTK_WIDGET( _widget ), oops.get_error_string() );
+ return true;
+}
+
+void
+file_selector::regerror (int code, regex_t *regex) const
+{
+ size_t length = ::regerror (code, regex, 0, 0);
+ char *message = new char[length];
+
+ ::regerror (code, regex, message, length);
+ fprintf (stderr, "%s\n", message);
+
+ delete[] message;
+}
+
+std::string
+file_selector::get_format_name () const
+{
+ return _format[_format_index].name;
+}
+
+iscan::file_format
+file_selector::get_type () const
+{
+ return _format[_format_index].type;
+}
+
+bool
+file_selector::multi_page_mode ()
+{
+ if (iscan::PDF == get_type () || iscan::TIF == get_type ())
+ return _multi_page_mode;
+
+ return false;
+}
+
+bool
+file_selector::has_left_edge_binding ()
+{
+ return _has_left_edge_binding;
+}
+
+gint
+file_selector::get_index (const char* name) const
+{
+ if (!name) return 0;
+
+ int i = 0;
+ while (_format[i].name && (0 != strcmp (name, _format[i].name)))
+ {
+ ++i;
+ }
+ return (_format[i].name ? i : 0);
+}
+
+bool
+file_selector::is_supported (const char* format) const
+{
+ // FIXME! ugly kludge to "check" for support
+ if (!format)
+ {
+ return false;
+ }
+ if (0 == strcmp ("PCX", format))
+ {
+ return iscan::pcxstream::is_usable ();
+ }
+ if (0 == strcmp ("PNM", format))
+ {
+ return iscan::pnmstream::is_usable ();
+ }
+ if (0 == strcmp ("PNG", format))
+ {
+ return iscan::pngstream::is_usable ();
+ }
+ if (0 == strcmp ("JPEG", format))
+ {
+ return iscan::jpegstream::is_usable ();
+ }
+ if (0 == strcmp ("PDF", format))
+ {
+ return iscan::pdfstream::is_usable ();
+ }
+ if (0 == strcmp ("TIFF", format))
+ {
+ return iscan::tiffstream::is_usable ();
+ }
+
+ return false;
+}
+
+#include <sstream>
+#include <iostream>
+bool
+file_selector::possible_overwrite (const std::string& current) const
+{
+ if (!_using_adf || _multi_page_mode)
+ return (0 == access (current.c_str (), F_OK));
+
+ string::size_type dot = current.rfind (file_opener::ext_sep);
+ string::size_type hash = current.find_last_not_of (file_opener::hash_mark,
+ dot - 1);
+ int digits = dot - ++hash;
+
+ stringstream ss;
+ ss << current.substr (0, hash)
+ << "([1-9][0-9]{" << digits << ",}|[0-9]{" << digits << "})\\"
+ << current.substr (dot);
+
+ return check_overwrite (ss.str ().c_str ());
+}
+
+int
+file_selector::check_overwrite (const char *regexp_in) const
+{
+ bool found = false;
+
+ if (!regexp_in)
+ return false;
+
+ char* regexp = strdup (regexp_in);
+ if (!regexp)
+ return true;
+
+ char *slash = strrchr( regexp, '/' );
+
+ if (!slash)
+ {
+ free (regexp);
+ return true;
+ }
+
+ *slash = '\0'; // regexp now holds the directory name
+ char dirname[ strlen( regexp )];
+ strcpy( dirname, regexp );
+
+ *slash = '^'; // re-anchor the regexp
+
+ regex_t *comp_regex = new regex_t;
+ int comp = regcomp( comp_regex, slash, REG_EXTENDED );
+
+ if (0 == comp)
+ {
+ size_t nsub = comp_regex->re_nsub + 1;
+ regmatch_t match[nsub];
+
+ DIR *dir = opendir( dirname );
+ if (!dir)
+ {
+ regfree( comp_regex );
+ delete comp_regex;
+ free (regexp);
+ return 0; // file creation failure handles this
+ }
+
+ struct dirent *file = 0;
+ while (!found && (file = readdir( dir )))
+ {
+ int result = regexec( comp_regex, file->d_name, nsub, match, 0 );
+ if (0 == result)
+ {
+ size_t digits = match[1].rm_eo - match[1].rm_so;
+ char num[digits + 1];
+ char *c = num;
+ {
+ char *p = file->d_name + match[1].rm_so;
+ while (0 < digits--)
+ *c++ = *p++;
+ }
+ int seq_num = atoi( num );
+
+ found = (seq_num >= get_sequence_number());
+ }
+ else
+ if (REG_NOMATCH != result)
+ regerror( comp, comp_regex );
+ }
+ closedir( dir );
+ }
+ else
+ regerror( comp, comp_regex );
+
+ regfree( comp_regex );
+ delete comp_regex;
+ free (regexp);
+
+ return found;
+}
diff --git a/frontend/file-selector.h b/frontend/file-selector.h
new file mode 100644
index 0000000..3c932cc
--- /dev/null
+++ b/frontend/file-selector.h
@@ -0,0 +1,135 @@
+/* file-selector.h -- customized file selection dialog -*- C++ -*-
+ Copyright (C) 2003, 2005, 2008 SEIKO EPSON CORPORATION
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+ */
+
+#ifndef ISCAN_FILE_SELECTOR_H
+#define ISCAN_FILE_SELECTOR_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <regex.h>
+#include <gtk/gtk.h>
+
+#include "pisa_error.h"
+
+#include "imgstream.hh" // for iscan::file_format
+
+#define G_CALLBACK_API public /* EEK! */
+
+class file_selector
+{
+public:
+ void init ();
+ void create_window (GtkWindow *window, GtkWidget *parent,
+ bool do_consecutive_scanning = false,
+ bool show_match_direction = false,
+ const char* default_format = NULL);
+
+ void destroy ();
+
+ char *get_pathname () const;
+
+ int get_sequence_number () const;
+
+ bool valid_pathname (const std::string& path) const;
+ bool save_pathname (); // really G_CALLBACK_API only
+
+ void hide () const;
+
+ std::string get_format_name () const;
+ iscan::file_format get_type () const;
+
+ bool multi_page_mode ();
+ bool has_left_edge_binding ();
+
+G_CALLBACK_API:
+
+ void destroy (bool really);
+ void delete_text (gint start, gint end);
+ void insert_text (gchar *text, gint len, gint *pos);
+ void change_format (int index);
+ void change_seqnum ();
+ void change_digits ();
+ void change_page_mode (bool mode);
+ void change_binding_edge (bool match);
+
+private:
+ char *canonize (const char *text) const;
+
+ void add_dialog_extensions (gint index = 0);
+
+ bool permission (const char *file) const;
+ bool show_message (const pisa_error& oops,
+ const char *yes = NULL,
+ const char *no = NULL) const;
+
+ void set_entry (const char *text);
+
+ void regerror (int code, regex_t *regex) const;
+
+ gint get_index (const char* name) const;
+ bool is_supported (const char* format) const;
+ bool possible_overwrite (const std::string& current) const;
+ int check_overwrite (const char *regexp) const;
+
+ GtkWidget *_parent;
+ GtkFileSelection *_widget;
+ char *_filename; // relative filename
+ char *_pathname; // absolute filename
+
+ bool _using_adf;
+ bool _do_duplex; // indicates whether to display a binding
+ // location selector (default: left edge)
+
+ int _format_index; // index into file format menu indicating
+ // the currently selected item
+ bool _deleted_all;
+ bool _multi_page_mode;
+ bool _has_left_edge_binding; // indicates whether the document is bound
+ // on the left hand side
+
+ int _number; // for consecutive scans
+ GtkAdjustment *_seqnum;
+ GtkAdjustment *_digits;
+
+ typedef struct // graphic format info
+ {
+ const char *name; // used in option menu
+ const char *lib; // library providing support
+ const char *ext; // default filename extension
+ const char *rgx; // filename extension regex
+ const iscan::file_format type;
+ } format;
+
+ static const format _format[];
+ const format **_fmt;
+
+ char *_ext_regex;
+};
+
+#endif /* ISCAN_FILE_SELECTOR_H */
diff --git a/frontend/gimp-plugin.h b/frontend/gimp-plugin.h
new file mode 100644
index 0000000..4e6a86c
--- /dev/null
+++ b/frontend/gimp-plugin.h
@@ -0,0 +1,186 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2005 SEIKO EPSON CORPORATION
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+#ifndef GIMP_PLUGIN_H
+#define GIMP_PLUGIN_H
+
+#ifdef HAVE_GIMP_2
+/* Define these structures for using gimp-devel-2 */
+
+struct _GimpParamColor_1
+{
+ guint8 red;
+ guint8 green;
+ guint8 blue;
+};
+typedef struct _GimpParamColor_1 GimpParamColor_1 ;
+
+union _GimpParamData_1
+{
+ gint32 d_int32;
+ gint16 d_int16;
+ gint8 d_int8;
+ gdouble d_float;
+ gchar *d_string;
+ gint32 *d_int32array;
+ gint16 *d_int16array;
+ gint8 *d_int8array;
+ gdouble *d_floatarray;
+ gchar **d_stringarray;
+ GimpParamColor_1 d_color;
+ GimpParamRegion d_region;
+ gint32 d_display;
+ gint32 d_image;
+ gint32 d_layer;
+ gint32 d_layer_mask;
+ gint32 d_channel;
+ gint32 d_drawable;
+ gint32 d_selection;
+ gint32 d_boundary;
+ gint32 d_path;
+ gint32 d_unit;
+ GimpParasite d_parasite;
+ gint32 d_tattoo;
+ GimpPDBStatusType d_status;
+};
+typedef union _GimpParamData_1 GimpParamData_1 ;
+
+struct _GimpParam_1
+{
+ GimpPDBArgType type;
+ GimpParamData_1 data;
+};
+typedef struct _GimpParam_1 GimpParam_1;
+
+typedef void (* GimpInitProc_1) (void);
+typedef void (* GimpQuitProc_1) (void);
+typedef void (* GimpQueryProc_1) (void);
+typedef void (* GimpRunProc_1) (gchar *name,
+ gint n_params,
+ GimpParam_1 *param,
+ gint *n_return_vals,
+ GimpParam_1 **return_vals);
+
+struct _GimpPlugInInfo_1
+{
+ /* called when the gimp application initially starts up */
+ GimpInitProc_1 init_proc;
+
+ /* called when the gimp application exits */
+ GimpQuitProc_1 quit_proc;
+
+ /* called by the gimp so that the plug-in can inform the
+ * gimp of what it does. (ie. installing a procedure database
+ * procedure).
+ */
+ GimpQueryProc_1 query_proc;
+
+ /* called to run a procedure the plug-in installed in the
+ * procedure database.
+ */
+ GimpRunProc_1 run_proc;
+};
+typedef struct _GimpPlugInInfo_1 GimpPlugInInfo_1;
+
+#else
+/* Define these structures for using gimp-devel-1 */
+
+struct _GimpRGB_2
+{
+ gdouble r, g, b, a;
+};
+typedef struct _GimpRGB_2 GimpRGB_2;
+
+union _GimpParamData_2
+{
+ gint32 d_int32;
+ gint16 d_int16;
+ gint8 d_int8;
+ gdouble d_float;
+ gchar *d_string;
+ gint32 *d_int32array;
+ gint16 *d_int16array;
+ gint8 *d_int8array;
+ gdouble *d_floatarray;
+ gchar **d_stringarray;
+ GimpRGB_2 d_color;
+ GimpParamRegion d_region;
+ gint32 d_display;
+ gint32 d_image;
+ gint32 d_layer;
+ gint32 d_layer_mask;
+ gint32 d_channel;
+ gint32 d_drawable;
+ gint32 d_selection;
+ gint32 d_boundary;
+ gint32 d_path;
+ gint32 d_unit;
+ GimpParasite d_parasite;
+ gint32 d_tattoo;
+ GimpPDBStatusType d_status;
+};
+typedef union _GimpParamData_2 GimpParamData_2 ;
+
+struct _GimpParam_2
+{
+ GimpPDBArgType type;
+ GimpParamData_2 data;
+};
+typedef struct _GimpParam_2 GimpParam_2;
+
+typedef void (* GimpInitProc_2) (void);
+typedef void (* GimpQuitProc_2) (void);
+typedef void (* GimpQueryProc_2) (void);
+typedef void (* GimpRunProc_2) (const gchar *name,
+ gint n_params,
+ const GimpParam_2 *param,
+ gint *n_return_vals,
+ GimpParam_2 **return_vals);
+
+struct _GimpPlugInInfo_2
+{
+ /* called when the gimp application initially starts up */
+ GimpInitProc_2 init_proc;
+
+ /* called when the gimp application exits */
+ GimpQuitProc_2 quit_proc;
+
+ /* called by the gimp so that the plug-in can inform the
+ * gimp of what it does. (ie. installing a procedure database
+ * procedure).
+ */
+ GimpQueryProc_2 query_proc;
+
+ /* called to run a procedure the plug-in installed in the
+ * procedure database.
+ */
+ GimpRunProc_2 run_proc;
+};
+typedef struct _GimpPlugInInfo_2 GimpPlugInInfo_2;
+
+#endif // HAVE_GIMP_2
+
+#endif /* GIMP_PLUGIN_H */
diff --git a/frontend/pisa_aleart_dialog.cc b/frontend/pisa_aleart_dialog.cc
new file mode 100644
index 0000000..5b0cb6a
--- /dev/null
+++ b/frontend/pisa_aleart_dialog.cc
@@ -0,0 +1,144 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#include <config.h>
+
+#include "gettext.h"
+#define _(msg_id) gettext (msg_id)
+
+#include <stdio.h>
+
+/*------------------------------------------------------------*/
+#include "pisa_aleart_dialog.h"
+
+/*------------------------------------------------------------*/
+static gint delete_event ( GtkWidget * widget, gpointer data )
+{
+ widget = widget;
+ data = data;
+
+ ::gtk_main_quit ( );
+
+ return TRUE;
+}
+
+/*------------------------------------------------------------*/
+void set_value ( GtkWidget * widget, gpointer data )
+{
+ aleart_dialog * dlg= ( aleart_dialog * ) data;
+
+ dlg->m_id = GPOINTER_TO_INT ( ::gtk_object_get_data ( GTK_OBJECT ( widget ),
+ "value" ) );
+
+ ::gtk_main_quit ( );
+}
+
+/*------------------------------------------------------------*/
+int aleart_dialog::message_box ( GtkWidget * parent,
+ const char * msg,
+ const char * btn1 ,
+ const char * btn2 )
+{
+ GtkWidget * top;
+ GtkWidget * vbox;
+ GtkWidget * label;
+ GtkWidget * button;
+ char * default_btn = _(" OK ");
+
+ top = ::gtk_dialog_new ( );
+ ::gtk_window_set_policy ( GTK_WINDOW ( top ),
+ FALSE, FALSE, TRUE );
+ ::gtk_window_set_title ( GTK_WINDOW ( top ), PACKAGE );
+ ::gtk_window_set_position ( GTK_WINDOW ( top ), GTK_WIN_POS_CENTER );
+ ::gtk_container_border_width ( GTK_CONTAINER ( GTK_DIALOG ( top )->vbox ),
+ 10 );
+ ::gtk_widget_realize ( top );
+
+ ::gtk_signal_connect ( GTK_OBJECT ( top ), "delete_event",
+ GTK_SIGNAL_FUNC ( ::delete_event ), 0 );
+
+ if ( parent != 0 )
+ ::gtk_window_set_transient_for ( GTK_WINDOW ( top ),
+ GTK_WINDOW ( parent ) );
+
+ vbox = GTK_DIALOG ( top )->vbox;
+
+ label = ::gtk_label_new ( _( msg ) );
+ ::gtk_label_set_line_wrap (GTK_LABEL (label), true);
+ ::gtk_box_pack_start ( GTK_BOX ( vbox ), label, TRUE, TRUE, 0 );
+ ::gtk_widget_show ( label );
+
+ if ( btn1 == 0 && btn2 == 0 )
+ btn2 = default_btn;
+
+ if ( btn1 != 0 )
+ {
+ button = ::gtk_button_new_with_label ( btn1 );
+ if ( btn2 == 0 )
+ {
+ GTK_WIDGET_SET_FLAGS ( button, GTK_CAN_DEFAULT );
+ }
+ ::gtk_box_pack_start ( GTK_BOX ( GTK_DIALOG ( top )->action_area ),
+ button, FALSE, FALSE, 5 );
+ ::gtk_object_set_data ( GTK_OBJECT ( button ), "value", ( gpointer ) 1 );
+ ::gtk_signal_connect ( GTK_OBJECT ( button ), "clicked",
+ GTK_SIGNAL_FUNC ( set_value ), this );
+ if ( btn2 == 0 )
+ ::gtk_widget_grab_default ( button );
+ ::gtk_widget_show ( button );
+
+ m_id = 1;
+ }
+
+ if ( btn2 != 0 )
+ {
+ button = ::gtk_button_new_with_label ( btn2 );
+ GTK_WIDGET_SET_FLAGS ( button, GTK_CAN_DEFAULT );
+ ::gtk_box_pack_start ( GTK_BOX ( GTK_DIALOG ( top )->action_area ),
+ button, FALSE, FALSE, 5 );
+ ::gtk_object_set_data ( GTK_OBJECT ( button ), "value", ( gpointer ) 2 );
+ ::gtk_signal_connect ( GTK_OBJECT ( button ), "clicked",
+ GTK_SIGNAL_FUNC ( set_value ), this );
+ ::gtk_widget_grab_default ( button );
+ ::gtk_widget_show ( button );
+
+ m_id = 2;
+ }
+
+ ::gtk_widget_show ( top );
+
+ ::gtk_grab_add ( top );
+ ::gtk_main ( );
+ ::gtk_grab_remove ( top );
+ ::gtk_widget_destroy ( top );
+
+ return m_id;
+}
+
diff --git a/frontend/pisa_aleart_dialog.h b/frontend/pisa_aleart_dialog.h
new file mode 100644
index 0000000..b5c7fcb
--- /dev/null
+++ b/frontend/pisa_aleart_dialog.h
@@ -0,0 +1,52 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_ALEART_DIALOG_H
+#define ___PISA_ALEART_DIALOG_H
+
+#include <gtk/gtk.h>
+
+class aleart_dialog
+{
+
+ public:
+
+ // operation
+ int message_box ( GtkWidget * parent,
+ const char * msg,
+ const char * btn1 = 0,
+ const char * btn2 = 0 );
+
+ int m_id;
+
+};
+
+#endif // ___PISA_ALEART_DIALOG_H
+
diff --git a/frontend/pisa_change_unit.cc b/frontend/pisa_change_unit.cc
new file mode 100644
index 0000000..49f88b5
--- /dev/null
+++ b/frontend/pisa_change_unit.cc
@@ -0,0 +1,101 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001, 2005 SEIKO EPSON CORPORATION
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#include "pisa_change_unit.h"
+
+const double CM_PER_INCH = 2.54; // exact, per definition
+
+
+double
+inches_reflect_zoom (double inch, long zoom)
+{
+ return inch * zoom / 100;
+}
+
+double
+inch2centi (double inch, long zoom)
+{
+ return (inch * CM_PER_INCH * zoom) / 100;
+}
+
+
+long
+inch2width (double inch, long resolution, long zoom, bool monochrome)
+{
+ int boundary = (monochrome ? 32 : 8);
+
+ return (inch2pixel (inch, resolution, zoom) / boundary) * boundary;
+}
+
+long
+inch2height (double inch, long resolution, long zoom)
+{
+ return inch2pixel (inch, resolution, zoom);
+}
+
+long
+inch2pixel (double inch, long resolution, long zoom)
+{
+ return (long) ((inch * resolution * zoom) / 100);
+}
+
+long
+inch2pixel (bool is_width,
+ double inch, long resolution, long zoom)
+{
+ return (is_width
+ ? inch2width (inch, resolution, zoom)
+ : inch2height (inch, resolution, zoom));
+}
+
+
+long
+centi2width (double centi, long resolution, long zoom)
+{
+ return (centi2pixel (centi, resolution, zoom) / 8) * 8;
+}
+
+long
+centi2height (double centi, long resolution, long zoom)
+{
+ return centi2pixel (centi, resolution, zoom);
+}
+
+long
+centi2pixel (double centi, long resolution, long zoom)
+{
+ return (long) ((centi * resolution * zoom) / (100 * CM_PER_INCH));
+}
+
+long
+centi2pixel (bool is_width,
+ double centi, long resolution, long zoom)
+{
+ return (is_width
+ ? centi2width (centi, resolution, zoom)
+ : centi2height (centi, resolution, zoom));
+}
diff --git a/frontend/pisa_change_unit.h b/frontend/pisa_change_unit.h
new file mode 100644
index 0000000..add6116
--- /dev/null
+++ b/frontend/pisa_change_unit.h
@@ -0,0 +1,50 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001, 2005 SEIKO EPSON CORPORATION
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_CHANGE_UNIT_H
+#define ___PISA_CHANGE_UNIT_H
+
+double inches_reflect_zoom ( double inch, long zoom );
+double inch2centi ( double inch, long zoom = 100 );
+
+// Convenience API to convert dimensions to pixels the same way as the
+// epkowa backend does.
+
+long inch2width (double inch, long resolution, long zoom = 100,
+ bool monochrome = false);
+long inch2height (double inch, long resolution, long zoom = 100);
+long inch2pixel (double inch, long resolution, long zoom = 100);
+long inch2pixel (bool is_width,
+ double inch, long resolution, long zoom = 100);
+
+long centi2width (double centi, long resolution, long zoom = 100);
+long centi2height (double centi, long resolution, long zoom = 100);
+long centi2pixel (double centi, long resolution, long zoom = 100);
+long centi2pixel (bool is_width,
+ double centi, long resolution, long zoom = 100);
+
+#endif // ___PISA_CHANGE_UNIT_H
diff --git a/frontend/pisa_configuration.cc b/frontend/pisa_configuration.cc
new file mode 100644
index 0000000..3a5f52b
--- /dev/null
+++ b/frontend/pisa_configuration.cc
@@ -0,0 +1,223 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#include <config.h>
+
+#include "gettext.h"
+#define _(msg_id) gettext (msg_id)
+
+/*------------------------------------------------------------*/
+#include <stdio.h>
+#include <string.h>
+
+/*------------------------------------------------------------*/
+#include "pisa_configuration.h"
+#include "pisa_view_manager.h"
+#include "pisa_error.h"
+#include "pisa_default_val.h"
+
+/*------------------------------------------------------------*/
+static gint delete_event ( GtkWidget * widget, gpointer data )
+{
+ widget = widget;
+ data = data;
+
+ ::g_view_manager->close_window ( ID_WINDOW_CONFIG, 1 );
+
+ return TRUE;
+}
+
+/*------------------------------------------------------------*/
+static void click_ok ( GtkWidget * widget, gpointer data )
+{
+ config_window * cf_cls;
+
+ widget = widget;
+
+ cf_cls = ( config_window * ) data;
+
+ cf_cls->m_ok = 1;
+
+ cf_cls->update_filename ( );
+
+ ::g_view_manager->close_window ( ID_WINDOW_CONFIG, 1 );
+}
+
+/*------------------------------------------------------------*/
+static void click_cancel ( GtkWidget * widget, gpointer data )
+{
+ config_window * cf_cls;
+
+ widget = widget;
+
+ cf_cls = ( config_window * ) data;
+
+ cf_cls->m_ok = 0;
+
+ ::g_view_manager->close_window ( ID_WINDOW_CONFIG, 1 );
+}
+
+
+/*------------------------------------------------------------*/
+int config_window::init ( void )
+{
+ int i;
+
+ for ( i = 0; i < WG_CONFIG_NUM; i++ )
+ m_widget [ i ] = 0;
+
+ m_ok = 0;
+ ::strcpy ( m_cmd, "" );
+
+ return PISA_ERR_SUCCESS;
+}
+
+/*------------------------------------------------------------*/
+GtkWidget * config_window::create_window ( GtkWidget * parent )
+{
+ GtkWidget * top;
+ GtkWidget * widget;
+
+ top = ::gtk_dialog_new ( );
+ ::gtk_window_set_policy ( GTK_WINDOW ( top ),
+ FALSE, FALSE, TRUE );
+ ::gtk_container_border_width ( GTK_CONTAINER ( GTK_DIALOG ( top )->vbox ),
+ 5 );
+ ::gtk_window_set_title ( GTK_WINDOW ( top ), _( "Configuration" ) );
+ ::gtk_widget_set_uposition ( top, POS_CONFIG_X, POS_CONFIG_Y );
+ ::gtk_widget_realize ( top );
+
+ ::gtk_signal_connect ( GTK_OBJECT ( top ), "delete_event",
+ GTK_SIGNAL_FUNC ( ::delete_event ), 0 );
+
+ m_widget [ WG_CONFIG_TOP ] = top;
+
+ widget = create_pips_path ( );
+ ::gtk_box_pack_start ( GTK_BOX ( GTK_DIALOG ( top )->vbox ),
+ widget, FALSE, FALSE, 5 );
+ ::gtk_widget_show ( widget );
+
+ create_action_area ( );
+
+ ::gtk_window_set_modal ( GTK_WINDOW ( top ), TRUE );
+ ::gtk_window_set_transient_for ( GTK_WINDOW ( top ), GTK_WINDOW ( parent ) );
+ ::gtk_widget_show ( top );
+
+ return top;
+}
+
+/*------------------------------------------------------------*/
+int config_window::close_window ( int destroy )
+{
+ if ( destroy && m_widget [ WG_CONFIG_TOP ] )
+ ::gtk_widget_destroy ( m_widget [ WG_CONFIG_TOP ] );
+
+ m_widget [ WG_CONFIG_TOP ] = 0;
+
+ return PISA_ERR_SUCCESS;
+}
+
+/*------------------------------------------------------------*/
+void config_window::update_filename ( void )
+{
+ ::strcpy ( m_cmd,
+ ::gtk_entry_get_text ( GTK_ENTRY ( m_widget [ WG_CONFIG_PATH ] ) ) );
+}
+
+/*------------------------------------------------------------*/
+GtkWidget * config_window::create_pips_path ( void )
+{
+ GtkWidget * frame;
+ GtkWidget * vbox;
+ GtkWidget * hbox;
+ GtkWidget * comment;
+ GtkWidget * edit;
+
+ frame = ::gtk_frame_new ( _( "Print Command" ) );
+ ::gtk_container_border_width ( GTK_CONTAINER ( frame ), 5 );
+
+ vbox = ::gtk_vbox_new ( FALSE, 5 );
+ ::gtk_container_border_width ( GTK_CONTAINER ( vbox ), 5 );
+ ::gtk_container_add ( GTK_CONTAINER ( frame ), vbox );
+ ::gtk_widget_show ( vbox );
+
+ hbox = ::gtk_hbox_new ( FALSE, 5 );
+ ::gtk_container_border_width ( GTK_CONTAINER ( hbox ), 5 );
+ ::gtk_box_pack_start ( GTK_BOX ( vbox ), hbox, FALSE, FALSE, 5 );
+ ::gtk_widget_show ( hbox );
+
+ edit = ::gtk_entry_new ( );
+ ::gtk_widget_set_usize ( edit, 250, 0 );
+ ::gtk_box_pack_start ( GTK_BOX ( hbox ), edit, FALSE, FALSE, 5 );
+ ::gtk_widget_show ( edit );
+
+ ::gtk_entry_set_max_length ( GTK_ENTRY ( edit ), 255 );
+ ::gtk_entry_set_text ( GTK_ENTRY ( edit ), m_cmd );
+
+ m_widget [ WG_CONFIG_PATH ] = edit;
+
+ comment = ::gtk_label_new (_("In order to print, your print system "
+ "must be able to handle the PNG file "
+ "format directly. CUPS or Photo Image "
+ "Print System (versions 1.3.1 or later) "
+ "do this by default."));
+ ::gtk_label_set_line_wrap ( GTK_LABEL ( comment ), TRUE );
+ ::gtk_box_pack_start ( GTK_BOX ( vbox ), comment, FALSE, FALSE, 0 );
+ ::gtk_widget_show ( comment );
+
+ return frame;
+}
+
+/*------------------------------------------------------------*/
+GtkWidget * config_window::create_action_area ( void )
+{
+ GtkWidget * button;
+
+ // OK
+ button = ::gtk_button_new_with_label ( _( " OK " ) );
+ GTK_WIDGET_SET_FLAGS ( button, GTK_CAN_DEFAULT );
+ ::gtk_box_pack_start ( GTK_BOX ( GTK_DIALOG ( m_widget [ WG_CONFIG_TOP ] )->action_area ),
+ button, FALSE, FALSE, 0 );
+ ::gtk_signal_connect ( GTK_OBJECT ( button ), "clicked",
+ GTK_SIGNAL_FUNC ( click_ok ), this );
+ ::gtk_widget_grab_default ( button );
+ ::gtk_widget_show ( button );
+
+ // cancel
+ button = ::gtk_button_new_with_label ( _( " Cancel " ) );
+ ::gtk_box_pack_start ( GTK_BOX ( GTK_DIALOG ( m_widget [ WG_CONFIG_TOP ] )->action_area ),
+ button, FALSE, FALSE, 0 );
+ ::gtk_signal_connect ( GTK_OBJECT ( button ), "clicked",
+ GTK_SIGNAL_FUNC ( click_cancel ), this );
+ ::gtk_widget_show ( button );
+
+ return 0;
+}
+
diff --git a/frontend/pisa_configuration.h b/frontend/pisa_configuration.h
new file mode 100644
index 0000000..9950147
--- /dev/null
+++ b/frontend/pisa_configuration.h
@@ -0,0 +1,63 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_CONFIGURATION_H
+#define ___PISA_CONFIGURATION_H
+
+#include <gtk/gtk.h>
+#include <pisa_enums.h>
+
+class config_window
+{
+ public:
+
+ // operation
+ int init ( void );
+ GtkWidget * create_window ( GtkWidget * parent );
+ int close_window ( int destroy );
+
+ void update_filename ( void );
+
+ int m_ok;
+ char m_cmd [ 256 ];
+
+ private:
+
+ // operation
+ GtkWidget * create_pips_path ( void );
+ GtkWidget * create_action_area ( void );
+
+ // attribute
+ GtkWidget * m_widget [ WG_CONFIG_NUM ];
+
+};
+
+#endif // ___PISA_CONFIGURATION_H
+
diff --git a/frontend/pisa_default_val.h b/frontend/pisa_default_val.h
new file mode 100644
index 0000000..fe4f9be
--- /dev/null
+++ b/frontend/pisa_default_val.h
@@ -0,0 +1,89 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_DEFAULT_VAL_H
+#define ___PISA_DEFAULT_VAL_H
+
+#include "esmod-wrapper.hh"
+
+#define PREFERENCE ".iscan_preference"
+
+// gamma
+#define DEFGAMMA ISCAN_DEFAULT_GAMMA
+#define MINGAMMA 0.5
+#define MAXGAMMA 5.0
+#define LINEGAMMA 0.17
+#define PAGEGAMMA 0.34
+
+// highlight
+#define DEFHIGHLIGHT ISCAN_DEFAULT_HILITE
+#define MINHIGHLIGHT 61
+#define MAXHIGHLIGHT 490
+#define LINEHIGHLIGHT 15
+#define PAGEHIGHLIGHT 30
+
+// shadow
+#define DEFSHADOW ISCAN_DEFAULT_SHADOW
+#define MINSHADOW 0
+#define MAXSHADOW 60
+#define LINESHADOW 1
+#define PAGESHADOW 2
+
+// threshold
+#define DEFTHRESHOLD ISCAN_DEFAULT_THRESHOLD
+#define MINTHRESHOLD 0
+#define MAXTHRESHOLD 255
+#define LINETHRESHOLD 1
+#define PAGETHRESHOLD 5
+
+// gray balance
+#define DEFGRAYBALANCE 0
+#define MINGRAYBALANCE 0
+#define MAXGRAYBALANCE 100
+#define LINEGRAYBALANCE 5
+#define PAGEGRAYBALANCE 25
+
+// saturation
+#define DEFSATURATION 0
+#define MINSATURATION -100
+#define MAXSATURATION 100
+#define LINESATURATION 5
+#define PAGESATURATION 25
+
+// window position
+#define POS_MAIN_X 20
+#define POS_MAIN_Y 20
+#define POS_CONFIG_X 100
+#define POS_CONFIG_Y 100
+#define POS_PRINT_X 100
+#define POS_PRINT_Y 100
+
+#endif // ___PISA_DEFAULT_VAL_H
+
diff --git a/frontend/pisa_enums.h b/frontend/pisa_enums.h
new file mode 100644
index 0000000..ab780e5
--- /dev/null
+++ b/frontend/pisa_enums.h
@@ -0,0 +1,248 @@
+/* pisa_enums.h
+ Copyright (C) 2001, 2004, 2008, 2009 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_ENUMS_H
+#define ___PISA_ENUMS_H
+
+/*! Bit flags indicating what scan sources are supported by the device.
+ */
+enum
+{
+ PISA_OP_NONE = 0,
+ PISA_OP_FLATBED = 1 << 0,
+ PISA_OP_ADF = 1 << 1,
+ PISA_OP_ADFDPLX = 1 << 2,
+ PISA_OP_TPU = 1 << 3,
+};
+
+enum
+{
+ PISA_FT_POSI,
+ PISA_FT_NEGA,
+ PISA_FT_REFLECT,
+};
+
+enum
+{
+ PISA_DFD_OFF,
+ PISA_DFD_STD,
+ PISA_DFD_THIN,
+};
+
+// destination
+typedef enum
+{
+ PISA_DE_FILE,
+ PISA_DE_PRINTER,
+ PISA_DE_GIMP
+} pisa_dest_type;
+
+// pixel type
+typedef enum
+{
+ PISA_PT_BW,
+ PISA_PT_GRAY,
+ PISA_PT_RGB
+} pisa_pixel_type;
+
+// bit depth
+typedef enum
+{
+ PISA_BD_1 = 1 << 0,
+ PISA_BD_8 = 1 << 1,
+ PISA_BD_12 = 1 << 2,
+ PISA_BD_14 = 1 << 3,
+ PISA_BD_16 = 1 << 4
+} pisa_bitdepth_type;
+
+// de-screening
+typedef enum
+{
+ PISA_DESCREEN_ON,
+ PISA_DESCREEN_OFF
+} pisa_descreening_type;
+
+// auto exposure option
+typedef enum
+{
+ PISA_AE_PHOTO,
+ PISA_AE_DOC,
+ PISA_AE_GRAYED
+} pisa_aeoption_type;
+
+// dropout
+typedef enum
+{
+ PISA_DO_NONE,
+ PISA_DO_RED,
+ PISA_DO_GREEN,
+ PISA_DO_BLUE
+} pisa_dropout_type;
+
+// mono option
+typedef enum
+{
+ PISA_MO_NONE,
+ PISA_MO_TET,
+ PISA_MO_AAS
+} pisa_monooption_type;
+
+// halftone
+typedef enum
+{
+ PISA_HT_NONE,
+ PISA_HT_TONEA,
+ PISA_HT_TONEB,
+ PISA_HT_TONEC,
+ PISA_HT_DITHERA,
+ PISA_HT_DITHERB,
+ PISA_HT_DITHERC,
+ PISA_HT_DITHERD,
+ PISA_HT_USERA,
+ PISA_HT_USERB
+} pisa_halftone_type;
+
+// unit
+typedef enum
+{
+ PISA_UNIT_INCHES,
+ PISA_UNIT_PIXELS,
+ PISA_UNIT_CM
+} pisa_unit_type;
+
+// cursor
+typedef enum
+{
+ PISA_CS_ARROW,
+ PISA_CS_HAND,
+ PISA_CS_CROSS,
+ PISA_CS_TOP,
+ PISA_CS_BOTTOM,
+ PISA_CS_LEFT,
+ PISA_CS_RIGHT,
+ PISA_CS_LEFTTOP,
+ PISA_CS_LEFTBOTTOM,
+ PISA_CS_RIGHTTOP,
+ PISA_CS_RIGHTBOTTOM
+} pisa_cursor_type;
+
+// cursor gamma table
+typedef enum {
+ PISA_CS_GM_X,
+ PISA_CS_GM_FLEUR,
+ PISA_CS_GM_TCROSS,
+} pisa_cursor_gm_type;
+
+// resize
+typedef enum
+{
+ PISA_RS_NN=1, // nearest neighbor
+ PISA_RS_BL, // bi linear
+ PISA_RS_BC, // bi cubic
+} pisa_rs_type;
+
+// sharp
+typedef enum
+{
+ PISA_SH_UMASK = 1, // unsharp mask
+ PISA_SH_GAUSS, // gauss
+ PISA_SH_UMASKY, // unsahrp mask y
+} pisa_sh_type;
+
+// window
+typedef enum
+{
+ ID_WINDOW_MAIN,
+ ID_WINDOW_PREV,
+ ID_WINDOW_CONFIG,
+} pisa_window_id;
+
+// main window
+enum widget_main_window
+{
+ WG_MAIN_TOP,
+ WG_MAIN_IMG_MENU,
+ WG_MAIN_RES_MENU,
+ WG_MAIN_WIDTH,
+ WG_MAIN_HEIGHT,
+ WG_MAIN_FOCUS_0,
+ WG_MAIN_FOCUS_25,
+ WG_MAIN_DFD,
+ WG_MAIN_DFD_BOX,
+ WG_MAIN_DFD_STD,
+ WG_MAIN_DFD_THIN,
+ WG_MAIN_NUM
+};
+
+// gamma correction
+#define GAMMA_RGB 0x00
+#define GAMMA_RED 0x01
+#define GAMMA_GRN 0x02
+#define GAMMA_BLU 0x03
+
+enum widget_gamma_window
+{
+ WG_GAMMA_TAB_RGB,
+ WG_GAMMA_TAB_RED,
+ WG_GAMMA_TAB_GRN,
+ WG_GAMMA_TAB_BLU,
+ WG_GAMMA_BOX_RGB,
+ WG_GAMMA_BOX_RED,
+ WG_GAMMA_BOX_GRN,
+ WG_GAMMA_BOX_BLU,
+ WG_GAMMA_RESET,
+ WG_GAMMA_NOTE,
+ WG_GAMMA_NUM
+};
+
+// configuration
+enum widget_configuration
+{
+ WG_CONFIG_TOP,
+ WG_CONFIG_PATH,
+ WG_CONFIG_NUM
+};
+
+// file select
+enum widget_file_select
+{
+ WG_FS_TOP,
+ WG_FS_NUM
+};
+
+// print window
+enum widget_print_window
+{
+ WG_PRINT_TOP,
+ WG_PRINT_NUM
+};
+
+#endif // ___PISA_ENUMS_H
+
+
diff --git a/frontend/pisa_error.cc b/frontend/pisa_error.cc
new file mode 100644
index 0000000..13cf51c
--- /dev/null
+++ b/frontend/pisa_error.cc
@@ -0,0 +1,178 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001, 2009 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#include <config.h>
+
+#include "gettext.h"
+#define _(msg_id) gettext (msg_id)
+
+/*------------------------------------------------------------*/
+#include "pisa_error.h"
+
+pisa_error::pisa_error( pisa_error_id status )
+ : m_id( status )
+{
+}
+
+pisa_error::pisa_error( SANE_Status status )
+ : m_id( pisa_error_id( status | 0xff00 ) )
+{
+}
+
+enum // FIXME: needs sync with epkowa.c!
+ {
+ EXT_SANE_STATUS_NONE,
+ EXT_SANE_STATUS_MULTI_FEED,
+ EXT_SANE_STATUS_TRAY_CLOSED,
+ EXT_SANE_STATUS_MAX,
+ };
+
+pisa_error::pisa_error (SANE_Status status, const sane_scan& ss)
+ : m_id (pisa_error_id (status | 0xff00))
+{
+ SANE_Word value = 0;
+
+ switch (m_id)
+ {
+ case PISA_STATUS_JAMMED:
+ ss.get_value ("ext-sane-status", static_cast<void *> (&value), true);
+ if (EXT_SANE_STATUS_MULTI_FEED == value)
+ m_id = PISA_ERR_MULTI_FEED;
+ break;
+ case PISA_STATUS_COVER_OPEN:
+ ss.get_value ("ext-sane-status", static_cast<void *> (&value), true);
+ if (EXT_SANE_STATUS_TRAY_CLOSED == value)
+ m_id = PISA_ERR_TRAY_CLOSED;
+ break;
+ default:
+ ;
+ }
+}
+
+const char * pisa_error::get_error_string ( void ) const
+{
+ switch (remap())
+ {
+ // Let's get the SANE status IDs out of the way first.
+ case PISA_STATUS_GOOD:
+ return _("Operation completed succesfully.");
+ case PISA_STATUS_UNSUPPORTED:
+ return _("Operation is not supported.");
+ case PISA_STATUS_CANCELLED:
+ return _("Operation was cancelled.");
+ case PISA_STATUS_DEVICE_BUSY:
+ return _("Device is busy---retry later.");
+ case PISA_STATUS_INVAL:
+ return _("Data or argument is invalid.");
+ case PISA_STATUS_EOF:
+ return _("No more data available (end-of-file).");
+ case PISA_STATUS_JAMMED:
+ return _("A paper jam occured. "
+ "Open the Automatic Document Feeder and remove any paper.");
+ case PISA_STATUS_NO_DOCS:
+ return _("Please load the document(s) into the Automatic Document "
+ "Feeder.");
+ case PISA_STATUS_COVER_OPEN:
+ return _("The automatic document feeder or scanner unit is open.\n"
+ "Please close it.");
+ case PISA_STATUS_IO_ERROR:
+ return _("Error during device I/O.");
+ case PISA_STATUS_NO_MEM:
+ return _("Out of memory.");
+ case PISA_STATUS_ACCESS_DENIED:
+ return _("Access to resource has been denied.");
+ // Now we add our own.
+ case PISA_ERR_OUTOFMEMORY:
+ return _( "There is not enough disk space for operation" );
+
+ case PISA_ERR_CONNECT:
+ return _( "Could not send command to scanner.\n"
+ "Check the scanner's status." );
+
+ case PISA_ERR_UNSUPPORT:
+ return _( "Scanner model not supported" );
+
+ case PISA_ERR_AREALARGE:
+ return _( "Selected area is too large for this resolution.\n"
+ "Reduce the selected area or resolution." );
+
+ case PISA_ERR_FILENAME:
+ return _( "Could not create file" );
+
+ case PISA_ERR_FILEOPEN:
+ return _( "Could not create file" );
+
+ case PISA_ERR_OVERWRITE:
+ return _( "A file with the same name already exists.\n"
+ "Click \"Overwrite\" to replace the file or "
+ "\"Cancel\" if you want to use another file name.");
+
+ case PISA_ERR_MRRESTOOHIGH:
+ return _( "The Image Type setting you selected cannot be used "
+ "with this resolution.\n"
+ "Reduce the Resolution or Scale setting." );
+
+ case PISA_ERR_TRAY_CLOSED:
+ return _("Tray cover is closed. Please open the tray cover and "
+ "then scan again.");
+
+ case PISA_ERR_MULTI_FEED:
+ return _("A multi page feed occurred in the auto document feeder.\n"
+ "Open the cover, remove the documents, and then try again. "
+ "If documents remain on the tray, remove them and then reload "
+ "them.");
+
+ default:
+ break;
+ }
+
+ return _( "Unexpected error occurred" );
+}
+
+pisa_error_id
+pisa_error::remap() const
+{
+ switch (m_id)
+ {
+ // do what pase_sane_scan.cc used (yuck!) to do, except for
+ // statuses that we really need to special case
+ case PISA_STATUS_UNSUPPORTED:
+ case PISA_STATUS_DEVICE_BUSY:
+ case PISA_STATUS_INVAL:
+ case PISA_STATUS_EOF:
+ case PISA_STATUS_IO_ERROR:
+ case PISA_STATUS_NO_MEM:
+ case PISA_STATUS_ACCESS_DENIED:
+ return PISA_ERR_CONNECT;
+ default:
+ return m_id;
+ }
+ return PISA_ERR_INVALID_ERROR_ID;
+}
diff --git a/frontend/pisa_error.h b/frontend/pisa_error.h
new file mode 100644
index 0000000..7961207
--- /dev/null
+++ b/frontend/pisa_error.h
@@ -0,0 +1,104 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001, 2008, 2009 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_ERROR_H
+#define ___PISA_ERROR_H
+
+#include "pisa_sane_scan.h"
+
+typedef enum
+{
+ PISA_ERR_SUCCESS,
+ PISA_ERR_PARAMETER,
+ PISA_ERR_OUTOFMEMORY,
+ PISA_ERR_CONNECT,
+ PISA_ERR_UNSUPPORT,
+ PISA_ERR_AREALARGE,
+ PISA_ERR_FILENAME,
+ PISA_ERR_FILEOPEN,
+ PISA_ERR_OVERWRITE,
+ PISA_ERR_MRRESTOOHIGH,
+ PISA_ERR_TRAY_CLOSED,
+ PISA_ERR_MULTI_FEED,
+ // Make sure we can use the SANE status symbols. To avoid name
+ // clashes, replace SANE with PISA and put them in another range.
+ PISA_STATUS_GOOD = 0xff00, // 0 Operation completed succesfully.
+ PISA_STATUS_UNSUPPORTED, // 1 Operation is not supported.
+ PISA_STATUS_CANCELLED, // 2 Operation was cancelled.
+ PISA_STATUS_DEVICE_BUSY, // 3 Device is busy---retry later.
+ PISA_STATUS_INVAL, // 4 Data or argument is invalid.
+ PISA_STATUS_EOF, // 5 No more data available (end-of-file).
+ PISA_STATUS_JAMMED, // 6 Document feeder jammed.
+ PISA_STATUS_NO_DOCS, // 7 Document feeder out of documents.
+ PISA_STATUS_COVER_OPEN, // 8 Scanner cover is open.
+ PISA_STATUS_IO_ERROR, // 9 Error during device I/O.
+ PISA_STATUS_NO_MEM, // 10 Out of memory.
+ PISA_STATUS_ACCESS_DENIED, // 11 Access to resource has been denied.
+
+ PISA_ERR_INVALID_ERROR_ID
+} pisa_error_id;
+
+class pisa_error
+{
+ public:
+
+ // constructor
+ pisa_error ( pisa_error_id id );
+ pisa_error ( SANE_Status id );
+ pisa_error (SANE_Status id, const sane_scan& ss);
+
+ bool operator== (const pisa_error& e) const;
+ bool operator!= (const pisa_error& e) const;
+
+ pisa_error_id get_error_id ( void ) const { return remap(); };
+ const char * get_error_string ( void ) const;
+
+ private:
+
+ // attribute
+ pisa_error_id m_id;
+
+ pisa_error_id remap() const;
+};
+
+inline
+bool pisa_error::operator== (const pisa_error& e) const
+{
+ return e.m_id == m_id;
+}
+
+inline
+bool pisa_error::operator!= (const pisa_error& e) const
+{
+ return !(e == *this);
+}
+
+#endif // ___PISA_ERROR_H
+
diff --git a/frontend/pisa_esmod_structs.h b/frontend/pisa_esmod_structs.h
new file mode 100644
index 0000000..027cd9c
--- /dev/null
+++ b/frontend/pisa_esmod_structs.h
@@ -0,0 +1,75 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_ESMOD_STRUCTES_H
+#define ___PISA_ESMOD_STRUCTES_H
+
+#include "pisa_enums.h"
+
+/*--------------------------------------------------------------*/
+struct img_size
+{
+ long in_width;
+ long in_height;
+ long in_rowbytes;
+
+ long out_width;
+ long out_height;
+ long out_rowbytes;
+
+ short bits_per_pixel;
+};
+
+/*--------------------------------------------------------------*/
+struct moire_img_info : public img_size
+{
+ long resolution;
+ long in_resolution;
+};
+
+/*--------------------------------------------------------------*/
+struct resize_img_info : public img_size
+{
+ pisa_rs_type resize_flag;
+
+ long resolution;
+};
+
+/*--------------------------------------------------------------*/
+struct sharp_img_info : public img_size
+{
+ unsigned long strength;
+ unsigned long radius;
+ unsigned long clipping;
+
+ pisa_sh_type sharp_flag;
+};
+
+#endif // ___PISA_ESMOD_STRUCTES_H
diff --git a/frontend/pisa_gamma_correction.cc b/frontend/pisa_gamma_correction.cc
new file mode 100644
index 0000000..83dd232
--- /dev/null
+++ b/frontend/pisa_gamma_correction.cc
@@ -0,0 +1,718 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001, 2005, 2008 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#include <config.h>
+
+#include "gettext.h"
+#define _(msg_id) gettext (msg_id)
+
+/*------------------------------------------------------------*/
+#include <gtk/gtk.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*------------------------------------------------------------*/
+#include "pisa_view_manager.h"
+#include "pisa_gamma_correction.h"
+#include "xpm_data.h"
+#include "pisa_tool.h"
+#include "pisa_error.h"
+
+#define RADIUS 3
+#define MIN_DISTANCE 8
+#define NUM_POINTS 13
+
+/*------------------------------------------------------------*/
+char ** g_gamma_xpm [ ] =
+{
+ gamma_m_xpm, // mono
+ gamma_r_xpm, // red
+ gamma_g_xpm, // green
+ gamma_b_xpm, // blue
+};
+
+/*------------------------------------------------------------*/
+static void switch_page ( GtkNotebook * notebook,
+ GtkNotebookPage * page,
+ gint page_num,
+ gpointer * data )
+{
+ gamma_correction * gamma_cls;
+
+ notebook = notebook;
+ page = page;
+
+ gamma_cls = ( gamma_correction * ) data;
+
+ gamma_cls->change_page ( page_num );
+}
+
+/*------------------------------------------------------------*/
+static void click_reset ( GtkWidget * widget,
+ gpointer * data )
+{
+ gamma_correction * gamma_cls;
+
+ widget = widget;
+
+ gamma_cls = ( gamma_correction * ) data;
+
+ gamma_cls->reset ( 0 );
+
+ ::g_view_manager->update_lut ( );
+
+ preview_window *prev_cls =
+ (preview_window *) ::g_view_manager->get_window_cls (ID_WINDOW_PREV);
+
+ prev_cls->update_img ( );
+}
+
+/*------------------------------------------------------------*/
+static gint gamma_curve_event ( GtkWidget * widget,
+ GdkEvent * event,
+ gpointer * data )
+{
+ gamma_correction * gamma_cls;
+
+ gamma_cls = ( gamma_correction * ) data;
+
+ return gamma_cls->event ( widget, event );
+}
+
+/*------------------------------------------------------------*/
+int gamma_correction::init ( void )
+{
+ int i;
+
+ m_page = 0;
+
+ for ( i = 0; i < WG_GAMMA_NUM; i++ )
+ m_widget [ i ] = 0;
+
+ for ( i = 0; i < 4; i++ )
+ {
+ m_pixmap [ i ] = 0;
+ m_active [ i ] = 0;
+
+ reset_points ( i );
+ }
+
+ m_basis [ 0 ] [ 0 ] = -0.5; m_basis [ 0 ] [ 1 ] = 1.5; m_basis [ 0 ] [ 2 ] = -1.5; m_basis [ 0 ] [ 3 ] = 0.5;
+ m_basis [ 1 ] [ 0 ] = 1.0; m_basis [ 1 ] [ 1 ] = -2.5; m_basis [ 1 ] [ 2 ] = 2.0; m_basis [ 1 ] [ 3 ] = -0.5;
+ m_basis [ 2 ] [ 0 ] = -0.5; m_basis [ 2 ] [ 1 ] = 0.0; m_basis [ 2 ] [ 2 ] = 0.5; m_basis [ 2 ] [ 3 ] = 0.0;
+ m_basis [ 3 ] [ 0 ] = 0.0; m_basis [ 3 ] [ 1 ] = 1.0; m_basis [ 3 ] [ 2 ] = 0.0; m_basis [ 3 ] [ 3 ] = 0.0;
+
+ for ( i = 0; i < 2; i++ )
+ m_cursor [ i ] = 0;
+
+ return PISA_ERR_SUCCESS;
+}
+
+/*------------------------------------------------------------*/
+GtkWidget * gamma_correction::create_controls ( GtkWidget * parent )
+{
+ GtkWidget * vbox;
+ GtkWidget * widget;
+
+ vbox = ::gtk_vbox_new ( FALSE, 2 );
+ widget = create_gamma_notebook ( parent );
+ ::gtk_box_pack_start ( GTK_BOX ( vbox ), widget, FALSE, FALSE, 0 );
+ ::gtk_widget_show ( widget );
+
+ widget = create_reset_button ( );
+ ::gtk_box_pack_start ( GTK_BOX ( vbox ), widget, FALSE, FALSE, 0 );
+ ::gtk_widget_show ( widget );
+
+ m_cursor [ PISA_CS_GM_X ] = ::gdk_cursor_new ( GDK_X_CURSOR );
+ m_cursor [ PISA_CS_GM_FLEUR ] = ::gdk_cursor_new ( GDK_FLEUR );
+ m_cursor [ PISA_CS_GM_TCROSS ] = ::gdk_cursor_new ( GDK_TCROSS );
+
+ return vbox;
+}
+
+/*------------------------------------------------------------*/
+int gamma_correction::close_window ( int destroy )
+{
+ int i;
+
+ destroy = destroy;
+
+ for ( i = 0; i < 2; i++ )
+ {
+ if ( m_cursor [ i ] )
+ ::gdk_cursor_destroy ( m_cursor [ i ] );
+ m_cursor [ i ] = 0;
+ }
+
+ return PISA_ERR_SUCCESS;
+}
+
+/*------------------------------------------------------------*/
+void gamma_correction::sensitive ( int is_prev_img )
+{
+ gboolean enable_rgb, enable_r_g_b;
+
+ settings set = g_view_manager->get_settings ();
+
+ // grayout
+ switch ( set.imgtype.pixeltype )
+ {
+ case PISA_PT_RGB:
+ enable_rgb = TRUE;
+ enable_r_g_b = TRUE;
+ break;
+ case PISA_PT_GRAY:
+ enable_rgb = TRUE;
+ enable_r_g_b = FALSE;
+ break;
+ case PISA_PT_BW:
+ enable_rgb = FALSE;
+ enable_r_g_b = FALSE;
+ break;
+ default:
+ return;
+ }
+
+ if ( is_prev_img == 0 )
+ enable_rgb = enable_r_g_b = FALSE;
+
+ m_active [ 0 ] = enable_rgb;
+ m_active [ 1 ] = enable_r_g_b;
+ m_active [ 2 ] = enable_r_g_b;
+ m_active [ 3 ] = enable_r_g_b;
+
+ ::gtk_widget_set_sensitive ( m_widget [ WG_GAMMA_TAB_RGB ], enable_rgb );
+ ::gtk_widget_set_sensitive ( m_widget [ WG_GAMMA_TAB_RED ], enable_r_g_b );
+ ::gtk_widget_set_sensitive ( m_widget [ WG_GAMMA_TAB_GRN ], enable_r_g_b );
+ ::gtk_widget_set_sensitive ( m_widget [ WG_GAMMA_TAB_BLU ], enable_r_g_b );
+
+ ::gtk_widget_set_sensitive ( m_widget [ WG_GAMMA_BOX_RGB ], enable_rgb );
+ ::gtk_widget_set_sensitive ( m_widget [ WG_GAMMA_BOX_RED ], enable_r_g_b );
+ ::gtk_widget_set_sensitive ( m_widget [ WG_GAMMA_BOX_GRN ], enable_r_g_b );
+ ::gtk_widget_set_sensitive ( m_widget [ WG_GAMMA_BOX_BLU ], enable_r_g_b );
+
+ if ( enable_rgb == TRUE && enable_r_g_b == FALSE )
+ ::gtk_notebook_set_page ( GTK_NOTEBOOK ( m_widget [ WG_GAMMA_NOTE ] ),
+ GAMMA_RGB );
+
+ ::gtk_widget_set_sensitive ( m_widget [ WG_GAMMA_RESET ], m_active [ m_page ] );
+
+ update_gamma ( );
+}
+
+/*------------------------------------------------------------*/
+void gamma_correction::change_page ( int page )
+{
+ m_page = page;
+
+ ::gtk_widget_set_sensitive ( m_widget [ WG_GAMMA_RESET ], m_active [ m_page ] );
+
+}
+
+
+/*------------------------------------------------------------*/
+void gamma_correction::reset ( int all )
+{
+ if ( all == 1 )
+ {
+ int i;
+
+ for ( i = 0; i < 4; i++ )
+ reset_points ( i );
+ }
+ else
+ reset_points ( m_page );
+
+ calculate_curve ( );
+ update_gamma ( );
+}
+
+/*------------------------------------------------------------*/
+gint gamma_correction::event ( GtkWidget * widget, GdkEvent * event )
+{
+ static int cursor_type = PISA_CS_GM_FLEUR;
+ int new_type;
+ GdkEventMotion * mevent = NULL;
+ gint tx, ty, x, y;
+ gint i, distance, closest_point;
+
+ ::gdk_window_get_pointer ( m_drawarea [ m_page ]->window,
+ & tx, & ty, 0 );
+ x = CLAMP ( ( tx - RADIUS ), 0, GAMMA_WIDTH - 1 );
+ y = CLAMP ( ( ty - RADIUS ), 0, GAMMA_HEIGHT - 1 );
+
+ distance = G_MAXINT;
+ closest_point = 0;
+
+ for ( i = 0; i < NUM_POINTS; i++ )
+ {
+ if ( m_points [ m_page ] [ i ] [ 0 ] != -1 )
+ if ( ::abs ( x - m_points [ m_page ] [ i ] [ 0 ] ) < distance )
+ {
+ distance = ::abs ( x - m_points [ m_page ] [ i ] [ 0 ] );
+ closest_point = i;
+ }
+ }
+
+ if ( distance > MIN_DISTANCE )
+ closest_point = ( x + 8 ) / 16;
+
+ switch ( event->type )
+ {
+ case GDK_EXPOSE:
+ if ( m_pixmap [ m_page ] == 0 )
+ m_pixmap [ m_page ] = ::gdk_pixmap_new ( m_drawarea [ m_page ]->window,
+ GAMMA_WIDTH + RADIUS * 2,
+ GAMMA_HEIGHT + RADIUS * 2,
+ -1 );
+ update_gamma ( );
+ break;
+
+ case GDK_BUTTON_PRESS:
+ new_type = PISA_CS_GM_TCROSS;
+
+ m_leftmost = -1;
+ for ( i = closest_point - 1; i >= 0; i-- )
+ if ( m_points [ m_page ] [ i ] [ 0 ] != -1 )
+ {
+ m_leftmost = m_points [ m_page ] [ i ] [ 0 ];
+ break;
+ }
+ m_rightmost = GAMMA_WIDTH;
+ for ( i = closest_point + 1; i < NUM_POINTS; i++ )
+ if ( m_points [ m_page ] [ i ] [ 0 ] != -1 )
+ {
+ m_rightmost = m_points [ m_page ] [ i ][ 0 ];
+ break;
+ }
+
+ m_grabpoint = closest_point;
+ m_points [ m_page ] [ m_grabpoint ] [ 0 ] = x;
+ m_points [ m_page ] [ m_grabpoint ] [ 1 ] = GAMMA_HEIGHT - 1 - y;
+
+ calculate_curve ( );
+ update_gamma ( );
+ gtk_grab_add ( widget );
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ new_type = PISA_CS_GM_FLEUR;
+ m_grabpoint = -1;
+ gtk_grab_remove ( widget );
+ ::g_view_manager->update_lut ( );
+ {
+ preview_window *prev_cls =
+ (preview_window *) ::g_view_manager->get_window_cls (ID_WINDOW_PREV);
+
+ prev_cls->update_img ( );
+ }
+ break;
+
+ case GDK_MOTION_NOTIFY:
+ mevent = ( GdkEventMotion * ) event;
+
+ if ( mevent->is_hint )
+ {
+ mevent->x = tx;
+ mevent->y = ty;
+ }
+
+ if ( m_grabpoint == -1 )
+ {
+ if ( m_points [ m_page ] [ closest_point ] [ 0 ] != -1 )
+ new_type = PISA_CS_GM_FLEUR;
+ else
+ new_type = PISA_CS_GM_TCROSS;
+ }
+ else
+ {
+ new_type = PISA_CS_GM_TCROSS;
+
+ m_points [ m_page ] [ m_grabpoint ] [ 0 ] = -1;
+
+ if ( x > m_leftmost && x < m_rightmost )
+ {
+ closest_point = ( x + 8 ) / 16;
+ if ( m_points [ m_page ] [ closest_point ] [ 0 ] == -1 )
+ m_grabpoint = closest_point;
+
+ m_points [ m_page ] [ m_grabpoint ] [ 0 ] = x;
+ m_points [ m_page ] [ m_grabpoint ] [ 1 ] = GAMMA_HEIGHT - 1 - y;
+ }
+ calculate_curve ( );
+ update_gamma ( );
+ }
+
+ if ( new_type != cursor_type )
+ {
+ cursor_type = new_type;
+ ::gdk_window_set_cursor ( m_drawarea [ m_page ]->window,
+ m_cursor [ cursor_type ] );
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+/*------------------------------------------------------------*/
+GtkWidget * gamma_correction::create_gamma_notebook ( GtkWidget * parent )
+{
+ GtkWidget * hbox;
+ GtkWidget * notebook;
+ GtkWidget * vbox;
+ GtkWidget * img;
+ GtkWidget * gamma_curve;
+ int i;
+
+ hbox = ::gtk_hbox_new ( FALSE, 2 );
+
+ notebook = ::gtk_notebook_new ( );
+ ::gtk_container_border_width ( GTK_CONTAINER ( notebook ), 2 );
+ ::gtk_box_pack_start ( GTK_BOX ( hbox ), notebook, TRUE, FALSE, 0 );
+
+ for ( i = 0; i < 4; i++ )
+ {
+ vbox = gtk_vbox_new ( FALSE, 5 );
+
+ img = ::xpm2widget ( parent, g_gamma_xpm [ i ] );
+
+ m_widget [ i ] = img;
+
+ ::gtk_notebook_append_page ( GTK_NOTEBOOK ( notebook ), vbox, img );
+ ::gtk_widget_show ( vbox );
+
+ gamma_curve = create_gamma_curve ( i );
+
+ m_widget [ i + 4 ] = gamma_curve;
+
+ ::gtk_box_pack_start ( GTK_BOX ( vbox ), gamma_curve, FALSE, FALSE, 0 );
+ ::gtk_widget_show ( gamma_curve );
+ }
+
+ ::gtk_signal_connect ( GTK_OBJECT ( notebook ),
+ "switch_page",
+ GTK_SIGNAL_FUNC ( ::switch_page ),
+ this );
+
+ ::gtk_widget_show ( notebook );
+
+ m_widget [ WG_GAMMA_NOTE ] = notebook;
+
+ return hbox;
+}
+
+/*------------------------------------------------------------*/
+GtkWidget * gamma_correction::create_gamma_curve ( int i )
+{
+ GtkWidget * frame;
+ GtkWidget * gamma_curve;
+
+ frame = ::gtk_frame_new ( 0 );
+ ::gtk_frame_set_shadow_type ( GTK_FRAME ( frame ), GTK_SHADOW_ETCHED_IN );
+
+ gamma_curve = ::gtk_drawing_area_new ( );
+ ::gtk_drawing_area_size ( GTK_DRAWING_AREA ( gamma_curve ),
+ GAMMA_WIDTH + RADIUS * 2,
+ GAMMA_HEIGHT + RADIUS * 2 );
+
+ ::gtk_widget_set_events ( gamma_curve,
+ GDK_EXPOSURE_MASK |
+ GDK_POINTER_MOTION_MASK |
+ GDK_POINTER_MOTION_HINT_MASK |
+ GDK_ENTER_NOTIFY_MASK |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_BUTTON1_MOTION_MASK );
+
+ m_drawarea [ i ] = gamma_curve;
+
+ ::gtk_signal_connect ( GTK_OBJECT ( gamma_curve ), "event",
+ GTK_SIGNAL_FUNC ( gamma_curve_event ), this );
+
+
+ ::gtk_container_add ( GTK_CONTAINER ( frame ), gamma_curve );
+
+ ::gtk_widget_show ( gamma_curve );
+
+ return frame;
+}
+
+/*------------------------------------------------------------*/
+GtkWidget * gamma_correction::create_reset_button ( void )
+{
+ GtkWidget * hbox;
+ GtkWidget * button;
+
+ hbox = ::gtk_hbox_new ( FALSE, 5 );
+ ::gtk_container_border_width ( GTK_CONTAINER ( hbox ), 5 );
+
+ button = ::gtk_button_new_with_label ( _( " Reset " ) );
+ ::gtk_box_pack_start ( GTK_BOX ( hbox ), button, TRUE, FALSE, 0 );
+ ::gtk_signal_connect ( GTK_OBJECT ( button ), "clicked",
+ GTK_SIGNAL_FUNC ( ::click_reset ), this );
+ ::gtk_widget_show ( button );
+
+ m_widget [ WG_GAMMA_RESET ] = button;
+
+ return hbox;
+}
+
+/*------------------------------------------------------------*/
+void gamma_correction::reset_points ( int i )
+{
+ int j;
+
+ m_grabpoint = -1;
+
+ for ( j = 0; j < NUM_POINTS; j++ )
+ {
+ m_points [ i ] [ j ] [ 0 ] = -1;
+ m_points [ i ] [ j ] [ 1 ] = -1;
+ }
+
+ for ( j = 0; j < GAMMA_WIDTH; j++ )
+ m_curve [ i ] [ j ] = j;
+
+ m_points [ i ] [ 0 ] [ 0 ] = 0;
+ m_points [ i ] [ 0 ] [ 1 ] = 0;
+ m_points [ i ] [ NUM_POINTS - 1 ] [ 0 ] = GAMMA_WIDTH - 1;
+ m_points [ i ] [ NUM_POINTS - 1 ] [ 1 ] = GAMMA_HEIGHT - 1;
+}
+
+/*------------------------------------------------------------*/
+void gamma_correction::update_gamma ( void )
+{
+ int i;
+ GdkPoint points [ GAMMA_WIDTH ];
+ GdkGC * gc;
+
+ if ( m_pixmap [ m_page ] == 0 )
+ return;
+
+ // clear the pixmap
+ ::gdk_draw_rectangle ( m_pixmap [ m_page ],
+ m_drawarea [ m_page ]->style->bg_gc [ GTK_STATE_NORMAL ],
+ TRUE, 0, 0,
+ GAMMA_WIDTH + RADIUS * 2, GAMMA_HEIGHT + RADIUS * 2 );
+
+ // draw the grid line
+ for ( i = 0; i < 13; i++ )
+ {
+ ::gdk_draw_line ( m_pixmap [ m_page ],
+ m_drawarea [ m_page ]->style->dark_gc [ GTK_STATE_NORMAL ],
+ RADIUS, i * ( GAMMA_HEIGHT / 12 ) + RADIUS,
+ GAMMA_WIDTH + RADIUS, i * ( GAMMA_HEIGHT / 12 ) + RADIUS );
+ ::gdk_draw_line ( m_pixmap [ m_page ],
+ m_drawarea [ m_page ]->style->dark_gc [ GTK_STATE_NORMAL ],
+ i * ( GAMMA_WIDTH / 12 ) + RADIUS, RADIUS,
+ i * ( GAMMA_WIDTH / 12 ) + RADIUS, GAMMA_HEIGHT + RADIUS );
+ }
+
+ if ( m_active [ m_page ] )
+ gc = m_drawarea [ m_page ]->style->black_gc;
+ else
+ gc = m_drawarea [ m_page ]->style->dark_gc [ GTK_STATE_NORMAL ];
+
+ // draw the curve
+ for ( i = 0; i < GAMMA_WIDTH; i++ )
+ {
+ points [ i ].x = i + RADIUS;
+ points [ i ].y = ( GAMMA_HEIGHT ) - m_curve [ m_page ] [ i ] + RADIUS;
+ }
+
+ ::gdk_draw_points ( m_pixmap [ m_page ],
+ gc, points, GAMMA_WIDTH );
+
+ // draw the points
+ for ( i = 0; i < NUM_POINTS; i++ )
+ {
+ if ( m_points [ m_page ] [ i ] [ 0 ] != -1 )
+ ::gdk_draw_arc ( m_pixmap [ m_page ],
+ gc,
+ TRUE,
+ m_points [ m_page ] [ i ] [ 0 ],
+ ( GAMMA_HEIGHT - 1 ) - m_points [ m_page ] [ i ] [ 1 ],
+ RADIUS * 2, RADIUS * 2, 0, 23040 );
+ }
+
+ ::gdk_draw_pixmap ( m_drawarea [ m_page ]->window,
+ m_drawarea [ m_page ]->style->black_gc,
+ m_pixmap [ m_page ],
+ 0, 0, 0, 0, GAMMA_WIDTH + RADIUS * 2,
+ GAMMA_HEIGHT + RADIUS * 2 );
+ return;
+}
+
+/*------------------------------------------------------------*/
+void gamma_correction::calculate_curve ( void )
+{
+ int i, num;
+ int p1, p2, p3, p4;
+ int points [ NUM_POINTS ];
+ marquee * marq;
+
+ num = 0;
+
+ for ( i = 0; i < NUM_POINTS; i++ )
+ if ( m_points [ m_page ] [ i ] [ 0 ] != -1 )
+ points [ num++ ] = i;
+
+ if ( 0 < num )
+ {
+ for ( i = 0; i < m_points [ m_page ] [ points [ 0 ] ] [ 0 ]; i++ )
+ {
+ m_curve [ m_page ] [ i ] =
+ m_points [ m_page ] [ points [ 0 ] ] [ 1 ];
+ }
+
+ for ( i = m_points [ m_page ] [ points [ num - 1 ] ] [ 0 ]; i < GAMMA_WIDTH; i++ )
+ {
+ m_curve [ m_page ] [ i ] =
+ m_points [ m_page ] [ points [ num - 1 ] ] [ 1 ];
+ }
+ }
+
+ for ( i = 0; i < num - 1; i++ )
+ {
+ p1 = ( i == 0 ) ? points [ i ] : points [ ( i - 1 ) ];
+ p2 = points [ i ];
+ p3 = points [ ( i + 1 ) ];
+ p4 = ( i == ( num - 2 ) ) ? points [ ( num - 1 ) ] : points [ ( i + 2 ) ];
+
+ plot_curve ( p1, p2, p3, p4 );
+ }
+
+ marq = & g_view_manager->get_marquee ();
+
+ for ( i = 0; i < 256; i++ )
+ marq->gamma_table [ m_page ] [ i ] =
+ 255 * ( m_curve [ m_page ] [ 191 * i / 255 ] ) / ( GAMMA_WIDTH - 1 );
+}
+
+/*------------------------------------------------------------*/
+void gamma_correction::plot_curve ( int p1, int p2, int p3, int p4 )
+{
+ matrix geometry;
+ matrix tmp1, tmp2;
+ matrix deltas;
+ double x, dx, dx2, dx3;
+ double y, dy, dy2, dy3;
+ double d, d2, d3;
+ int lastx, lasty;
+ int newx, newy;
+ int i;
+
+ for ( i = 0; i < 4; i++ )
+ {
+ geometry [ i ] [ 0 ] = 0;
+ geometry [ i ] [ 1 ] = 0;
+ geometry [ i ] [ 2 ] = 0;
+ geometry [ i ] [ 3 ] = 0;
+ }
+
+ for ( i = 0; i < 2; i++ )
+ {
+ geometry [ 0 ] [ i ] = m_points [ m_page ] [ p1 ] [ i ];
+ geometry [ 1 ] [ i ] = m_points [ m_page ] [ p2 ] [ i ];
+ geometry [ 2 ] [ i ] = m_points [ m_page ] [ p3 ] [ i ];
+ geometry [ 3 ] [ i ] = m_points [ m_page ] [ p4 ] [ i ];
+ }
+
+ d = 1.0 / 1000;
+ d2 = d * d;
+ d3 = d * d * d;
+
+ tmp2 [ 0 ] [ 0 ] = 0; tmp2 [ 0 ] [ 1 ] = 0; tmp2 [ 0 ] [ 2 ] = 0; tmp2 [ 0 ] [ 3 ] = 1;
+ tmp2 [ 1 ] [ 0 ] = d3; tmp2 [ 1 ] [ 1 ] = d2; tmp2 [ 1 ] [ 2 ] = d; tmp2 [ 1 ] [ 3 ] = 0;
+ tmp2 [ 2 ] [ 0 ] = 6 * d3; tmp2 [ 2 ] [ 1 ] = 2 * d2; tmp2 [ 2 ] [ 2 ] = 0; tmp2 [ 2 ] [ 3 ] = 0;
+ tmp2 [ 3 ] [ 0 ] = 6 * d3; tmp2 [ 3 ] [ 1 ] = 0; tmp2 [ 3 ] [ 2 ] = 0; tmp2 [ 3 ] [ 3 ] = 0;
+
+ compose_curve ( m_basis, geometry, tmp1 );
+
+ compose_curve ( tmp2, tmp1, deltas );
+
+ x = deltas [ 0 ] [ 0 ];
+ dx = deltas [ 1 ] [ 0 ];
+ dx2 = deltas [ 2 ] [ 0 ];
+ dx3 = deltas [ 3 ] [ 0 ];
+
+ y = deltas [ 0 ] [ 1 ];
+ dy = deltas [ 1 ] [ 1 ];
+ dy2 = deltas [ 2 ] [ 1 ];
+ dy3 = deltas [ 3 ] [ 1 ];
+
+ lastx = ( int ) ( CLAMP ( x, 0, GAMMA_WIDTH - 1 ) );
+ lasty = ( int ) ( CLAMP ( y, 0, GAMMA_HEIGHT - 1 ));
+
+ m_curve [ m_page ] [ lastx ] = lasty;
+
+ for ( i = 0; i < 1000; i++ )
+ {
+ x += dx;
+ dx += dx2;
+ dx2 += dx3;
+
+ y += dy;
+ dy += dy2;
+ dy2 += dy3;
+
+ newx = CLAMP ( int ( x + 0.5 ), 0, GAMMA_WIDTH - 1 );
+ newy = CLAMP ( int ( y + 0.5 ), 0, GAMMA_HEIGHT - 1 );
+
+ if ( ( lastx != newx ) || ( lasty != newy ) )
+ m_curve [ m_page ] [ newx ] = newy;
+
+ lastx = newx;
+ lasty = newy;
+ }
+}
+
+/*------------------------------------------------------------*/
+void gamma_correction::compose_curve ( matrix a, matrix b, matrix ab )
+{
+ int i, j;
+
+ for ( i = 0; i < 4; i++ )
+ {
+ for ( j = 0; j < 4; j++ )
+ {
+ ab [ i ] [ j ] = ( a [ i ] [ 0 ] * b [ 0 ] [ j ] +
+ a [ i ] [ 1 ] * b [ 1 ] [ j ] +
+ a [ i ] [ 2 ] * b [ 2 ] [ j ] +
+ a [ i ] [ 3 ] * b [ 3 ] [ j ] );
+ }
+ }
+}
diff --git a/frontend/pisa_gamma_correction.h b/frontend/pisa_gamma_correction.h
new file mode 100644
index 0000000..6848620
--- /dev/null
+++ b/frontend/pisa_gamma_correction.h
@@ -0,0 +1,85 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_GAMMA_CORRECTION_H
+#define ___PISA_GAMMA_CORRECTION_H
+
+#include <gtk/gtk.h>
+#include "pisa_enums.h"
+
+#define GAMMA_WIDTH 192
+#define GAMMA_HEIGHT 192
+
+class gamma_correction
+{
+ public:
+
+ // operation
+ int init ( void );
+ GtkWidget * create_controls ( GtkWidget * parent );
+ int close_window ( int destroy );
+ void sensitive ( int is_prev_img );
+
+ void change_page ( int page );
+ void reset ( int all );
+ gint event ( GtkWidget * widget, GdkEvent * event );
+
+ private:
+
+ typedef double matrix [ 4 ] [ 4 ];
+
+ // operation
+ GtkWidget * create_gamma_notebook ( GtkWidget * parent );
+ GtkWidget * create_gamma_curve ( int i );
+ GtkWidget * create_reset_button ( void );
+
+ void reset_points ( int i );
+ void update_gamma ( void );
+ void calculate_curve ( void );
+ void plot_curve ( int p1, int p2, int p3, int p4 );
+ void compose_curve ( matrix a, matrix b, matrix ab );
+
+ // attribute
+ int m_page;
+ GtkWidget * m_widget [ WG_GAMMA_NUM ];
+ GtkWidget * m_drawarea [ 4 ];
+ GdkPixmap * m_pixmap [ 4 ];
+
+ int m_leftmost, m_rightmost;
+ int m_points [ 4 ] [ 17 ] [ 2 ];
+ unsigned char m_curve[ 4 ] [ GAMMA_WIDTH ];
+ int m_grabpoint;
+ matrix m_basis;
+ int m_active [ 4 ];
+
+ GdkCursor * m_cursor [ 3 ];
+};
+
+#endif // ___PISA_GAMMA_CORRECTION_H
diff --git a/frontend/pisa_gimp.cc b/frontend/pisa_gimp.cc
new file mode 100644
index 0000000..c674c9f
--- /dev/null
+++ b/frontend/pisa_gimp.cc
@@ -0,0 +1,665 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001, 2005, 2009 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#include <gtk/gtk.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <dlfcn.h>
+
+#include "pisa_main.h"
+#include "pisa_gimp.h"
+#include "pisa_error.h"
+#include "pisa_enums.h"
+
+#ifdef HAVE_ANY_GIMP
+/*----------------------------------------------------------*/
+static void * handle_libgimp = 0;
+int g_gimpversion = 1;
+
+typedef gchar* lib_gimp_version (void);
+typedef gint lib_gimp_main ( gint argc, gchar * argv [ ] );
+typedef void lib_gimp_quit ( void );
+typedef void lib_gimp_install_procedure ( gchar * name,
+ gchar * blurb,
+ gchar * help,
+ gchar * author,
+ gchar * copyright,
+ gchar * date,
+ gchar * menu_path,
+ gchar * image_types,
+ gint type,
+ gint nparams,
+ gint nreturn_vals,
+ GimpParamDef * params,
+ GimpParamDef * return_vals );
+typedef guint lib_gimp_tile_height ( void );
+typedef gint32 lib_gimp_image_new ( gint width,
+ gint height,
+ GimpImageBaseType type );
+typedef gint32 lib_gimp_layer_new ( gint32 image_ID,
+ gchar * name,
+ gint width,
+ gint height,
+ GimpImageType type,
+ gdouble opacity,
+ GimpLayerModeEffects mode );
+typedef gboolean lib_gimp_image_add_layer ( gint32 image_ID,
+ gint32 layer_ID,
+ gint position );
+typedef GimpDrawable * lib_gimp_drawable_get ( gint32 drawable_ID );
+typedef void lib_gimp_pixel_rgn_init (GimpPixelRgn *pr,
+ GimpDrawable *drawable,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ gint dirty,
+ gint shadow);
+typedef void lib_gimp_pixel_rgn_set_rect (GimpPixelRgn *pr,
+ guchar *buf,
+ gint x,
+ gint y,
+ gint width,
+ gint height );
+typedef gboolean lib_gimp_image_remove_layer (gint32 image_ID,
+ gint32 layer_ID );
+typedef gboolean lib_gimp_image_delete ( gint32 image_ID );
+typedef void lib_gimp_drawable_flush ( GimpDrawable *drawable );
+typedef void lib_gimp_drawable_detach ( GimpDrawable *drawable );
+typedef gint32 lib_gimp_display_new ( gint32 image_ID );
+typedef gchar * lib_gimp_gtkrc ( void );
+typedef gboolean lib_gimp_use_xshm ( void );
+
+// Define function prototype for plug-in gimp-2
+typedef void lib_gimp_extension_ack_2 ( void );
+typedef void lib_gimp_install_procedure_2 (const gchar * name,
+ const gchar * blurb,
+ const gchar * help,
+ const gchar * author,
+ const gchar * copyright,
+ const gchar * date,
+ const char * menu_path,
+ const gchar * image_types,
+ GimpPDBProcType type,
+ gint nparams,
+ gint nreturn_vals,
+ const GimpParamDef * params,
+ const GimpParamDef * return_vals );
+
+#ifdef HAVE_GIMP_2
+typedef gint lib_gimp_main_2 ( const GimpPlugInInfo *info,
+ gint argc, gchar * argv [ ] );
+#else
+typedef gint lib_gimp_main_2 ( const GimpPlugInInfo_2 *info,
+ gint argc, gchar * argv [ ] );
+#endif
+
+lib_gimp_version * plib_gimp_version;
+lib_gimp_main * plib_gimp_main;
+lib_gimp_quit * plib_gimp_quit;
+lib_gimp_install_procedure * plib_gimp_install_procedure;
+lib_gimp_tile_height * plib_gimp_tile_height;
+lib_gimp_image_new * plib_gimp_image_new;
+lib_gimp_layer_new * plib_gimp_layer_new;
+lib_gimp_image_add_layer * plib_gimp_image_add_layer;
+lib_gimp_drawable_get * plib_gimp_drawable_get;
+lib_gimp_pixel_rgn_init * plib_gimp_pixel_rgn_init;
+lib_gimp_pixel_rgn_set_rect * plib_gimp_pixel_rgn_set_rect;
+lib_gimp_image_remove_layer * plib_gimp_image_remove_layer;
+lib_gimp_image_delete * plib_gimp_image_delete;
+lib_gimp_drawable_flush * plib_gimp_drawable_flush;
+lib_gimp_drawable_detach * plib_gimp_drawable_detach;
+lib_gimp_display_new * plib_gimp_display_new;
+lib_gimp_gtkrc * plib_gimp_gtkrc;
+lib_gimp_use_xshm * plib_gimp_use_xshm;
+
+/* Use for plug-in gimp-2 */
+lib_gimp_main_2 * plib_gimp_main_2;
+lib_gimp_install_procedure_2 * plib_gimp_install_procedure_2;
+lib_gimp_extension_ack_2* plib_gimp_extension_ack_2;
+
+
+static void query ( void );
+#ifdef HAVE_GIMP_2
+static void run ( char * name, int nparams, GimpParam_1 * param,
+ int * nreturn_vals, GimpParam_1 ** return_vals );
+#else
+static void run ( char * name, int nparams, GimpParam * param,
+ int * nreturn_vals, GimpParam ** return_vals );
+#endif // HAVE_GIMP_2
+
+static void gimp2_query ( void );
+#ifdef HAVE_GIMP_2
+static void gimp2_run ( const gchar *name,
+ gint n_params,
+ const GimpParam *param,
+ gint *n_return_vals,
+ GimpParam **return_vals);
+#else
+static void gimp2_run ( const gchar *name,
+ gint n_params,
+ const GimpParam_2 *param,
+ gint *n_return_vals,
+ GimpParam_2 **return_vals);
+
+#endif // HAVE_GIMP_2
+
+/*----------------------------------------------------------*/
+// Define these structures for supporting with gimp-1 and gimp-2
+#ifdef HAVE_GIMP_2
+GimpPlugInInfo_1 PLUG_IN_INFO =
+{
+ 0,
+ 0,
+ query,
+ run,
+};
+GimpPlugInInfo PLUG_IN_INFO_2 =
+{
+ 0,
+ 0,
+ gimp2_query,
+ gimp2_run,
+};
+#else
+GimpPlugInInfo PLUG_IN_INFO =
+{
+ 0, // init
+ 0, // quit
+ query, // query
+ run, // run
+};
+GimpPlugInInfo_2 PLUG_IN_INFO_2 = {
+ 0,
+ 0,
+ gimp2_query,
+ gimp2_run,
+};
+#endif // HAVE_GIMP_2
+
+#endif // HAVE_ANY_GIMP
+
+/*----------------------------------------------------------*/
+gint pisa_gimp_main ( gint argc, gchar * argv [ ] )
+{
+#ifdef HAVE_ANY_GIMP
+ int i, j, k;
+ char libname [ 32 ];
+
+ for( k = 1; k <= 2; k++){
+
+ for ( i = 9; 0 <= i; i-- )
+ {
+ if ( handle_libgimp )
+ break;
+
+ for ( j = 9; 0 <= j; j-- )
+ {
+ sprintf ( libname, "libgimp-%d.%d.so.%d", k, i, j );
+ handle_libgimp = dlopen ( libname, RTLD_LAZY );
+ if ( handle_libgimp ){
+ g_gimpversion = k;
+ break;
+ }
+ }
+ }
+ }
+ if ( handle_libgimp == 0 )
+ return 1;
+
+ if ( g_gimpversion == 1){
+ plib_gimp_main = ( lib_gimp_main * ) dlsym ( handle_libgimp, "gimp_main" );
+ plib_gimp_install_procedure = ( lib_gimp_install_procedure * ) dlsym ( handle_libgimp, "gimp_install_procedure" );
+
+ // This function isn't supported in gimp-2
+ plib_gimp_use_xshm = ( lib_gimp_use_xshm * ) dlsym ( handle_libgimp, "gimp_use_xshm" );
+
+ if ( ! plib_gimp_main || ! plib_gimp_install_procedure || ! plib_gimp_use_xshm) {
+ return 1;
+ }
+
+ }
+ else{
+ // These functions are changed in gimp-2
+ plib_gimp_main_2 = ( lib_gimp_main_2 * ) dlsym ( handle_libgimp, "gimp_main" );
+ plib_gimp_install_procedure_2 = ( lib_gimp_install_procedure_2 * ) dlsym ( handle_libgimp, "gimp_install_procedure" );
+
+ // This is a new function in gimp-2
+ plib_gimp_extension_ack_2 = ( lib_gimp_extension_ack_2 *) dlsym(handle_libgimp, "gimp_extension_ack") ;
+
+ if ( ! plib_gimp_main_2 || ! plib_gimp_install_procedure_2 || ! plib_gimp_extension_ack_2 ) {
+ return 1;
+ }
+ }
+
+ plib_gimp_version = ( lib_gimp_version * ) dlsym ( handle_libgimp, "gimp_version" );
+ plib_gimp_quit = ( lib_gimp_quit * ) dlsym ( handle_libgimp, "gimp_quit" );
+ plib_gimp_tile_height = ( lib_gimp_tile_height * ) dlsym ( handle_libgimp, "gimp_tile_height" );
+ plib_gimp_image_new = ( lib_gimp_image_new * ) dlsym ( handle_libgimp, "gimp_image_new" );
+ plib_gimp_layer_new = ( lib_gimp_layer_new * ) dlsym ( handle_libgimp, "gimp_layer_new" );
+ plib_gimp_image_add_layer = ( lib_gimp_image_add_layer * ) dlsym ( handle_libgimp, "gimp_image_add_layer" );
+ plib_gimp_drawable_get = ( lib_gimp_drawable_get * ) dlsym ( handle_libgimp, "gimp_drawable_get" );
+ plib_gimp_pixel_rgn_init = ( lib_gimp_pixel_rgn_init * ) dlsym ( handle_libgimp, "gimp_pixel_rgn_init" );
+ plib_gimp_pixel_rgn_set_rect = ( lib_gimp_pixel_rgn_set_rect * ) dlsym ( handle_libgimp, "gimp_pixel_rgn_set_rect" );
+ plib_gimp_image_remove_layer = ( lib_gimp_image_remove_layer * ) dlsym ( handle_libgimp, "gimp_image_remove_layer" );
+ plib_gimp_image_delete = ( lib_gimp_image_delete * ) dlsym ( handle_libgimp, "gimp_image_delete" );
+ plib_gimp_drawable_flush = ( lib_gimp_drawable_flush * ) dlsym ( handle_libgimp, "gimp_drawable_flush" );
+ plib_gimp_drawable_detach = ( lib_gimp_drawable_detach * ) dlsym ( handle_libgimp, "gimp_drawable_detach" );
+ plib_gimp_display_new = ( lib_gimp_display_new * ) dlsym ( handle_libgimp, "gimp_display_new" );
+ plib_gimp_gtkrc = ( lib_gimp_gtkrc * ) dlsym ( handle_libgimp, "gimp_gtkrc" );
+
+ if ( ! plib_gimp_quit || ! plib_gimp_tile_height || ! plib_gimp_image_new || ! plib_gimp_layer_new ||
+ ! plib_gimp_image_add_layer || ! plib_gimp_drawable_get ||
+ ! plib_gimp_pixel_rgn_init || ! plib_gimp_pixel_rgn_set_rect ||
+ ! plib_gimp_image_remove_layer || ! plib_gimp_image_delete ||
+ ! plib_gimp_drawable_flush || ! plib_gimp_drawable_detach ||
+ ! plib_gimp_display_new || ! plib_gimp_gtkrc )
+ return 1;
+
+ if (g_gimpversion == 1){
+ return plib_gimp_main ( argc, argv );
+ }
+ else{
+ return plib_gimp_main_2 (&PLUG_IN_INFO_2, argc, argv );
+ }
+
+#else
+ return 1;
+#endif // HAVE_ANY_GIMP
+}
+
+/*----------------------------------------------------------*/
+void pisa_gimp_quit ( void )
+{
+#ifdef HAVE_ANY_GIMP
+ plib_gimp_quit ( );
+ dlclose ( handle_libgimp );
+#endif
+}
+
+#ifdef HAVE_ANY_GIMP
+
+/*----------------------------------------------------------*/
+gchar * pisa_gimp_gtkrc ( void )
+{
+ return plib_gimp_gtkrc ( );
+}
+
+/*----------------------------------------------------------*/
+gboolean pisa_gimp_use_xshm ( void )
+{
+ return plib_gimp_use_xshm ( );
+}
+
+/*----------------------------------------------------------*/
+static void query ( void )
+{
+ static GimpParamDef args [ ] =
+ {
+ { GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive" },
+ };
+
+ static GimpParamDef * return_vals = 0;
+ static int nargs = sizeof ( args ) / sizeof ( args [ 0 ] );
+ static int nreturn_vals = 0;
+
+ plib_gimp_install_procedure (
+ "Image Scan! for Linux",
+ "Front-end to the SANE interface",
+ "This function provides access to scanners "
+ "and other image acquisition devices through "
+ "the SANE (Scanner Access Now Easy) interface.",
+ "AVASYS CORPORATION",
+ "SEIKO EPSON CORPORATION",
+ "2001, 2005",
+ "<Toolbox>/Xtns/Acquire Image/Scanning (iscan)...",
+ "RGB, GRAY",
+ GIMP_EXTENSION,
+ nargs, nreturn_vals,
+ args, return_vals );
+}
+/*----------------------------------------------------------*/
+static void gimp2_query ( void )
+{
+ static GimpParamDef args [ ] =
+ {
+ { GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive" },
+ };
+
+ static GimpParamDef * return_vals = 0;
+ static int nargs = sizeof ( args ) / sizeof ( args [ 0 ] );
+ static int nreturn_vals = 0;
+
+ char* menu = "<Toolbox>/File/Acquire/Scanning (iscan)...";
+ int maj, min = 0;
+ if (!plib_gimp_version ||
+ 2 != sscanf (plib_gimp_version (), "%d.%d.", &maj, &min))
+ {
+ maj = 2;
+ min = 0;
+ }
+ if (2 < maj || (2 == maj && 6 <= min))
+ {
+ // accommodate new menu structure introduced in gimp 2.6
+ menu = "<Image>/File/Create/Acquire/Scanning (iscan)...";
+ }
+
+ // Must call this function for supporting gimp-2
+ plib_gimp_install_procedure_2 (
+ "Image Scan! for Linux",
+ "Front-end to the SANE interface",
+ "This function provides access to scanners "
+ "and other image acquisition devices through "
+ "the SANE (Scanner Access Now Easy) interface.",
+ "AVASYS CORPORATION",
+ "SEIKO EPSON CORPORATION",
+ "2009",
+ menu,
+ "",
+ GIMP_EXTENSION,
+ nargs, nreturn_vals,
+ args, return_vals );
+}
+
+/*----------------------------------------------------------*/
+
+#ifdef HAVE_GIMP_2
+static void run ( char * name, int nparams, GimpParam_1 * param,
+ int * nreturn_vals, GimpParam_1 ** return_vals )
+#else
+static void run ( char * name, int nparams, GimpParam * param,
+ int * nreturn_vals, GimpParam ** return_vals )
+
+#endif // HAVE_GIMP_2
+{
+#ifdef HAVE_GIMP_2
+ static GimpParam_1 values [ 1 ];
+ GimpRunMode run_mode;
+#else
+ static GimpParam values [ 1 ];
+ GimpRunModeType run_mode;
+#endif // HAVE_GIMP_2
+ char * argv [ 1 ];
+ int argc = 1;
+
+ name = name;
+ nparams = nparams;
+
+#ifdef HAVE_GIMP_2
+ run_mode = ( GimpRunMode ) param [ 0 ].data.d_int32;
+#else
+ run_mode = ( GimpRunModeType ) param [ 0 ].data.d_int32;
+#endif
+
+ * nreturn_vals = 1;
+ * return_vals = values;
+
+ values [ 0 ].type = GIMP_PDB_STATUS;
+ values [ 0 ].data.d_status = GIMP_PDB_SUCCESS;
+
+ argv [ 0 ] = "iscan";
+
+ switch ( run_mode )
+ {
+ case GIMP_RUN_INTERACTIVE:
+ pisa_start ( argc, argv, 1);
+ break;
+
+ default:
+ break;
+ }
+
+ return;
+}
+
+#ifdef HAVE_GIMP_2
+static void gimp2_run ( const gchar *name,
+ gint n_params,
+ const GimpParam *param,
+ gint *n_return_vals,
+ GimpParam **return_vals)
+#else
+static void gimp2_run ( const gchar *name,
+ gint n_params,
+ const GimpParam_2 *param,
+ gint *n_return_vals,
+ GimpParam_2 **return_vals)
+
+#endif// HAVE_GIMP_2
+{
+#ifdef HAVE_GIMP_2
+ static GimpParam values [ 1 ];
+ GimpRunMode run_mode;
+#else
+ static GimpParam_2 values [ 1 ];
+ GimpRunModeType run_mode;
+#endif// HAVE_GIMP_2
+ char * argv [ 1 ];
+ int argc = 1;
+
+ name = name;
+ n_params = n_params;
+
+#ifdef HAVE_GIMP_2
+ run_mode = ( GimpRunMode ) param [ 0 ].data.d_int32;
+#else
+ run_mode = ( GimpRunModeType ) param [ 0 ].data.d_int32;
+#endif // HAVE_GIMP_2
+
+ * n_return_vals = 1;
+ * return_vals = values;
+
+ values [ 0 ].type = GIMP_PDB_STATUS;
+ values [ 0 ].data.d_status = GIMP_PDB_SUCCESS;
+
+ argv [ 0 ] = "iscan";
+
+ switch ( run_mode )
+ {
+ case GIMP_RUN_INTERACTIVE:
+ // This procedure must be called with GIMP_EXTENSION
+ plib_gimp_extension_ack_2();
+ pisa_start ( argc, argv, 2);
+ break;
+ default:
+ break;
+ }
+
+ return;
+}
+
+/*----------------------------------------------------------*/
+int gimp_scan::create_gimp_image ( int width,
+ int height,
+ int pixeltype,
+ int depth )
+{
+ size_t tile_size;
+ GimpImageBaseType image_type;
+ GimpImageType drawable_type;
+
+ // initialize member variable
+ m_rows = 0;
+ m_width = width;
+ m_height = height;
+ m_pixeltype = pixeltype;
+ m_depth = depth;
+ m_rowbytes = 0;
+
+ m_tile = 0;
+ m_drawable = 0;
+
+ tile_size = width * ::plib_gimp_tile_height ( );
+
+ if ( pixeltype == PISA_PT_RGB )
+ {
+ tile_size *= 3;
+ image_type = GIMP_RGB;
+ drawable_type = GIMP_RGB_IMAGE;
+ m_rowbytes = width * 3;
+ }
+ else if ( pixeltype == PISA_PT_GRAY || pixeltype == PISA_PT_BW )
+ {
+ image_type = GIMP_GRAY;
+ drawable_type = GIMP_GRAY_IMAGE;
+ m_rowbytes = width;
+ }
+ else
+ return PISA_ERR_PARAMETER;
+
+ m_image_id = ::plib_gimp_image_new ( m_width, m_height, image_type );
+
+ m_layer_id = ::plib_gimp_layer_new ( m_image_id, "Background",
+ m_width, m_height, drawable_type,
+ 100.0, GIMP_NORMAL_MODE );
+ ::plib_gimp_image_add_layer ( m_image_id, m_layer_id, 0 );
+
+ m_drawable = ::plib_gimp_drawable_get ( m_layer_id );
+
+ ::plib_gimp_pixel_rgn_init ( & m_region, m_drawable, 0, 0,
+ m_drawable->width, m_drawable->height,
+ TRUE, FALSE );
+
+ m_tile = g_new ( guchar, tile_size );
+
+ if ( m_tile == 0 )
+ return PISA_ERR_OUTOFMEMORY;
+
+ return PISA_ERR_SUCCESS;
+}
+
+/*----------------------------------------------------------*/
+unsigned char * gimp_scan::get_next_buf ( void )
+{
+ int tile_height = ::plib_gimp_tile_height ( );
+
+ return & m_tile [ ( m_rows % tile_height ) * m_rowbytes ];
+}
+
+/*----------------------------------------------------------*/
+int gimp_scan::set_image_rect ( void )
+{
+ int tile_height = ::plib_gimp_tile_height ( );
+
+ m_rows++;
+
+ if ( m_rows % tile_height == 0 )
+ {
+ if ( m_depth == 1 )
+ bw2gray ( );
+
+ ::plib_gimp_pixel_rgn_set_rect ( & m_region, m_tile,
+ 0, m_rows - tile_height,
+ m_width, tile_height );
+ }
+
+ return PISA_ERR_SUCCESS;
+}
+
+/*----------------------------------------------------------*/
+int gimp_scan::finish_scan ( int cancel )
+{
+ int remaining;
+
+ if ( cancel )
+ {
+ ::plib_gimp_image_remove_layer ( m_image_id, m_layer_id );
+ ::plib_gimp_image_delete ( m_image_id );
+ ::free ( m_tile );
+ }
+ else
+ {
+ remaining = m_rows % ::plib_gimp_tile_height ( );
+
+ if ( remaining )
+ {
+ if ( m_depth == 1 )
+ bw2gray ( );
+
+ ::plib_gimp_pixel_rgn_set_rect ( & m_region, m_tile,
+ 0, m_rows - remaining,
+ m_width, remaining );
+ }
+
+ ::plib_gimp_drawable_flush ( m_drawable );
+ ::plib_gimp_display_new ( m_image_id );
+ ::plib_gimp_drawable_detach ( m_drawable );
+ ::free ( m_tile );
+ }
+
+ return PISA_ERR_SUCCESS;
+}
+
+/*----------------------------------------------------------*/
+int gimp_scan::bw2gray ( void )
+{
+ unsigned char * tmp;
+ unsigned char * bw_buf;
+ long bw_buf_size;
+ long i, j, k, tile_height;
+
+ tile_height = ::plib_gimp_tile_height ( );
+ bw_buf_size = ( ( m_width + 7 ) / 8 );
+
+ bw_buf = new unsigned char [ bw_buf_size ];
+
+ if ( ! bw_buf )
+ return PISA_ERR_OUTOFMEMORY;
+
+ for ( i = 0; i < tile_height; i++ )
+ {
+ ::memcpy ( bw_buf, m_tile + m_width * i, bw_buf_size );
+
+ tmp = bw_buf;
+
+ for ( j = 0; j < m_width; j += 8 )
+ {
+ for ( k = 7; k >= 0; k-- )
+ {
+ if ( j + 7 - k < m_width )
+ {
+ m_tile [ i * m_width + j + ( 7 - k ) ] = ( * tmp & ( 1 << k ) ) ?
+ 0x00 : 0xff;
+ }
+ }
+ tmp++;
+ }
+ }
+
+ delete [ ] bw_buf;
+
+ return PISA_ERR_SUCCESS;
+}
+
+#endif // HAVE_ANY_GIMP
+
+
diff --git a/frontend/pisa_gimp.h b/frontend/pisa_gimp.h
new file mode 100644
index 0000000..7e04c75
--- /dev/null
+++ b/frontend/pisa_gimp.h
@@ -0,0 +1,109 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_GIMP_H
+#define ___PISA_GIMP_H
+
+#include <config.h>
+#include <gtk/gtk.h>
+
+#ifdef HAVE_ANY_GIMP
+#include <libgimp/gimp.h>
+#include "gimp-plugin.h"
+
+#ifndef HAVE_GIMP_2
+#ifdef HAVE_LIBGIMP_GIMPFEATURES_H
+#include <libgimp/gimpfeatures.h>
+#else
+#define GIMP_CHECK_VERSION(major, minor, micro) 0
+#endif /* HAVE_LIBGIMP_GIMPFEATURES_H */
+
+#ifdef GIMP_CHECK_VERSION
+#if GIMP_CHECK_VERSION(1,1,25)
+ /* ok, we have the new gimp interface */
+#else
+ /* we have the old gimp interface and need the compatibility header file */
+#include "pisa_gimp_1_0_patch.h"
+#endif
+#else
+ /* we have the old gimp interface and need the compatibility header file */
+#include "pisa_gimp_1_0_patch.h"
+#endif /*GIMP_CHECK_VERSION*/
+#endif /* HAVE_GIMP_2 */
+#endif /* HAVE_ANY_GIMP */
+
+gint pisa_gimp_main ( gint argc, gchar * argv [ ] );
+void pisa_gimp_quit ( void );
+
+#ifdef HAVE_ANY_GIMP
+
+gchar * pisa_gimp_gtkrc ( void );
+gboolean pisa_gimp_use_xshm ( void );
+
+class gimp_scan
+{
+ public:
+
+ // operation
+ int create_gimp_image ( int width,
+ int height,
+ int pixeltype,
+ int depth );
+
+ unsigned char * get_next_buf ( void );
+ int set_image_rect ( void );
+ int finish_scan ( int cancel );
+
+ private:
+
+ // operation
+ int bw2gray ( void );
+
+ // attribute
+ int m_rows;
+
+ int m_width;
+ int m_height;
+ int m_pixeltype;
+ int m_depth;
+ int m_rowbytes;
+
+ int m_image_id;
+ int m_layer_id;
+ guchar * m_tile;
+ GimpDrawable * m_drawable;
+ GimpPixelRgn m_region;
+
+};
+
+#endif // HAVE_ANY_GIMP
+
+
+#endif // ___PISA_GIMP_H
diff --git a/frontend/pisa_gimp_1_0_patch.h b/frontend/pisa_gimp_1_0_patch.h
new file mode 100644
index 0000000..0d45078
--- /dev/null
+++ b/frontend/pisa_gimp_1_0_patch.h
@@ -0,0 +1,58 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_GIMP_1_0_PATCH_H
+#define ___PISA_GIMP_1_0_PATCH_H
+
+#define GimpPlugInInfo GPlugInInfo
+#define GimpParam GParam
+#define GimpParamDef GParamDef
+#define GimpDrawable GDrawable
+#define GimpPixelRgn GPixelRgn
+#define GimpRunModeType GRunModeType
+#define GimpImageType GImageType
+
+#define GIMP_PDB_INT32 PARAM_INT32
+#define GIMP_PDB_STATUS PARAM_STATUS
+#define GIMP_PDB_CALLING_ERROR STATUS_CALLING_ERROR
+#define GIMP_PDB_SUCCESS STATUS_SUCCESS
+#define GIMP_RUN_INTERACTIVE RUN_INTERACTIVE
+#define GIMP_RUN_NONINTERACTIVE RUN_NONINTERACTIVE
+#define GIMP_RUN_WITH_LAST_VALS RUN_WITH_LAST_VALS
+#define GIMP_EXTENSION PROC_EXTENSION
+#define GIMP_RGB RGB
+#define GIMP_RGB_IMAGE RGB_IMAGE
+#define GIMP_GRAY GRAY
+#define GIMP_GRAY_IMAGE GRAY_IMAGE
+#define GIMP_RGBA_IMAGE RGBA_IMAGE
+#define GIMP_NORMAL_MODE NORMAL_MODE
+
+#endif // ___PISA_GIMP_1_0_PATCH_H
+
diff --git a/frontend/pisa_image_controls.cc b/frontend/pisa_image_controls.cc
new file mode 100644
index 0000000..1889f1d
--- /dev/null
+++ b/frontend/pisa_image_controls.cc
@@ -0,0 +1,381 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001, 2005, 2008 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#include <config.h>
+
+#include "gettext.h"
+#define _(msg_id) gettext (msg_id)
+
+/*------------------------------------------------------------*/
+#include <gtk/gtk.h>
+#include <stdio.h>
+#include <string.h>
+
+/*------------------------------------------------------------*/
+#include "pisa_view_manager.h"
+#include "pisa_image_controls.h"
+#include "pisa_tool.h"
+#include "pisa_error.h"
+#include "pisa_structs.h"
+#include "pisa_default_val.h"
+
+/*------------------------------------------------------------*/
+static void value_changed ( GtkAdjustment * adjust, gpointer data );
+
+/*------------------------------------------------------------*/
+#define ID_SCALE_GAMMA 0x0001
+#define ID_SCALE_HIGHLIGHT 0x0002
+#define ID_SCALE_SHADOW 0x0003
+#define ID_SCALE_THRESHOLD 0x0004
+#define ID_SCALE_BRIGHTNESS 0x0005
+#define ID_SCALE_CONTRAST 0x0006
+
+scale_items g_scale_gamma = { ID_SCALE_GAMMA, 0, 0,
+ ( GtkSignalFunc * ) value_changed,
+ DEFGAMMA,
+ MINGAMMA,
+ MAXGAMMA,
+ LINEGAMMA,
+ PAGEGAMMA };
+scale_items g_scale_highlight = { ID_SCALE_HIGHLIGHT, 0, 0,
+ ( GtkSignalFunc * ) value_changed,
+ DEFHIGHLIGHT,
+ MINHIGHLIGHT,
+ MAXHIGHLIGHT,
+ LINEHIGHLIGHT,
+ PAGEHIGHLIGHT };
+scale_items g_scale_shadow = { ID_SCALE_SHADOW, 0, 0,
+ ( GtkSignalFunc * ) value_changed,
+ DEFSHADOW,
+ MINSHADOW,
+ MAXSHADOW,
+ LINESHADOW,
+ PAGESHADOW };
+scale_items g_scale_threshold = { ID_SCALE_THRESHOLD, 0, 0,
+ ( GtkSignalFunc * ) value_changed,
+ DEFTHRESHOLD,
+ MINTHRESHOLD,
+ MAXTHRESHOLD,
+ LINETHRESHOLD,
+ PAGETHRESHOLD };
+scale_items g_scale_brightness = { ID_SCALE_BRIGHTNESS, 0, 0,
+ ( GtkSignalFunc * ) value_changed,
+ 0, /* get value from backend */
+ 0, /* get range.max from backend */
+ 0, /* get range.min from backend */
+ 0, /* get range.quant from backend */
+ 0 };
+scale_items g_scale_contrast = { ID_SCALE_CONTRAST, 0, 0,
+ ( GtkSignalFunc * ) value_changed,
+ 0, /* get value from backend */
+ 0, /* get range.max from backend */
+ 0, /* get range.min from backend */
+ 0, /* get range.quant from backend */
+ 0 };
+
+/*------------------------------------------------------------*/
+
+static void value_changed ( GtkAdjustment * adjust, gpointer data )
+{
+ long id;
+ long val;
+ int change;
+ marquee * marq;
+
+ id = * ( long * ) data;
+ change = 1;
+ marq = & g_view_manager->get_marquee ();
+
+ switch ( id )
+ {
+ case ID_SCALE_GAMMA:
+ val = ( long ) ( adjust->value * 100 );
+ if ( marq->gamma == val )
+ change = 0;
+ else
+ marq->gamma = val;
+ break;
+
+ case ID_SCALE_HIGHLIGHT:
+ val = ( long ) ( adjust->value );
+ if ( marq->highlight == val )
+ change = 0;
+ else
+ marq->highlight = val;
+ break;
+
+ case ID_SCALE_SHADOW:
+ val = ( long ) ( adjust->value );
+ if ( marq->shadow == val )
+ change = 0;
+ else
+ marq->shadow = val;
+ break;
+
+ case ID_SCALE_THRESHOLD:
+ val = ( long ) ( adjust->value );
+ if ( marq->threshold == val )
+ change = 0;
+ else
+ marq->threshold = val;
+ break;
+
+ case ID_SCALE_BRIGHTNESS:
+ val = ( long ) ( adjust->value );
+ if ( marq->brightness != val )
+ {
+ marq->brightness = val;
+ }
+ change = 0;
+ break;
+
+ case ID_SCALE_CONTRAST:
+ val = ( long ) ( adjust->value );
+ if ( marq->contrast != val )
+ {
+ marq->contrast = val;
+ }
+ change = 0;
+ break;
+ }
+
+ if ( change )
+ {
+ ::g_view_manager->update_lut ( );
+ preview_window *prev_cls =
+ (preview_window *) ::g_view_manager->get_window_cls (ID_WINDOW_PREV);
+
+ prev_cls->update_img ( );
+ }
+}
+
+/*------------------------------------------------------------*/
+void image_controls::sensitive ( int is_prev_img )
+{
+ marquee * marq;
+ gboolean enable_color, enable_bw;
+ gboolean enable_brightness_contrast;
+ scan_manager * scan_mgr;
+
+ scan_mgr = g_view_manager->get_scan_manager ();
+
+ marq = & g_view_manager->get_marquee ();
+
+ // update value
+ ::gtk_adjustment_set_value ( ::g_scale_gamma.adjust, marq->gamma / 100.0f );
+ ::gtk_adjustment_set_value ( ::g_scale_highlight.adjust, marq->highlight );
+ ::gtk_adjustment_set_value ( ::g_scale_shadow.adjust, marq->shadow );
+ ::gtk_adjustment_set_value ( ::g_scale_threshold.adjust, marq->threshold );
+ ::gtk_adjustment_set_value ( ::g_scale_brightness.adjust, marq->brightness );
+ ::gtk_adjustment_set_value ( ::g_scale_contrast.adjust, marq->contrast );
+
+ // grayout
+ if ( is_prev_img
+ && g_view_manager->get_settings ().imgtype.pixeltype != PISA_PT_BW )
+ enable_color = TRUE;
+ else
+ enable_color = FALSE;
+
+ if ( is_prev_img
+ && g_view_manager->get_settings ().imgtype.pixeltype == PISA_PT_BW )
+ enable_bw = TRUE;
+ else
+ enable_bw = FALSE;
+
+ ::gtk_widget_set_sensitive ( ::g_scale_gamma.widget, enable_color );
+ ::gtk_widget_set_sensitive ( ::g_scale_highlight.widget, enable_color );
+ ::gtk_widget_set_sensitive ( ::g_scale_shadow.widget, enable_color );
+ ::gtk_widget_set_sensitive ( ::g_scale_threshold.widget, enable_bw );
+
+ // reset brightness, contrast
+ enable_brightness_contrast = TRUE;
+ if ( is_prev_img )
+ {
+ ::gtk_range_set_value ( GTK_RANGE (::g_scale_brightness.widget ),
+ brightness_default );
+ ::gtk_range_set_value ( GTK_RANGE (::g_scale_contrast.widget ),
+ contrast_default );
+ enable_brightness_contrast = FALSE;
+ }
+ ::gtk_widget_set_sensitive ( ::g_scale_brightness.widget,
+ ( enable_brightness_contrast
+ && scan_mgr->has_brightness() ) );
+ ::gtk_widget_set_sensitive ( ::g_scale_contrast.widget,
+ ( enable_brightness_contrast
+ && scan_mgr->has_contrast() ) );
+}
+
+/*------------------------------------------------------------*/
+GtkWidget * image_controls::create_controls ( void )
+{
+ GtkWidget * vbox;
+ GtkWidget * table;
+ GtkWidget * label;
+ GtkWidget * scale;
+ GtkWidget * separator;
+ scan_manager * scan_mgr;
+
+ scan_mgr = g_view_manager->get_scan_manager ();
+
+ table = ::gtk_table_new ( 4, 2, FALSE );
+ ::gtk_container_border_width ( GTK_CONTAINER ( table ), 5 );
+ ::gtk_table_set_row_spacings ( GTK_TABLE ( table ), 3 );
+ ::gtk_table_set_col_spacings ( GTK_TABLE ( table ), 5 );
+
+ // gamma
+ label = ::gtk_label_new ( _( "Gamma:" ) );
+ ::gtk_misc_set_alignment ( GTK_MISC ( label ), 1.0, 0.5 );
+ ::gtk_table_attach ( GTK_TABLE ( table ), label, 0, 1, 0, 1,
+ GTK_FILL, GTK_FILL, 0, 0 );
+ ::gtk_widget_show ( label );
+
+ scale = ::pisa_create_scale ( & ::g_scale_gamma );
+ ::gtk_widget_set_usize ( scale, 200, -1 );
+ ::gtk_scale_set_digits ( GTK_SCALE ( scale ), 2 );
+ ::gtk_range_set_update_policy ( GTK_RANGE ( scale ), GTK_UPDATE_DELAYED );
+ ::gtk_table_attach ( GTK_TABLE ( table ), scale, 1, 2, 0, 1,
+ GTK_FILL, GTK_FILL, 0, 0 );
+ ::gtk_widget_show ( scale );
+
+ // highlight
+ label = ::gtk_label_new ( _( "Highlight:" ) );
+ ::gtk_misc_set_alignment ( GTK_MISC ( label ), 1.0, 0.5 );
+ ::gtk_table_attach ( GTK_TABLE ( table ), label, 0, 1, 1, 2,
+ GTK_FILL, GTK_FILL, 0, 0 );
+ ::gtk_widget_show ( label );
+
+ scale = ::pisa_create_scale ( & ::g_scale_highlight );
+ ::gtk_widget_set_usize ( scale, 200, -1 );
+ ::gtk_scale_set_digits ( GTK_SCALE ( scale ), 0 );
+ ::gtk_range_set_update_policy ( GTK_RANGE ( scale ), GTK_UPDATE_DELAYED );
+ ::gtk_table_attach ( GTK_TABLE ( table ), scale, 1, 2, 1, 2,
+ GTK_FILL, GTK_FILL, 0, 0 );
+ ::gtk_widget_show ( scale );
+
+ // shadow
+ label = ::gtk_label_new ( _( "Shadow:" ) );
+ ::gtk_misc_set_alignment ( GTK_MISC ( label ), 1.0, 0.5 );
+ ::gtk_table_attach ( GTK_TABLE ( table ), label, 0, 1, 2, 3,
+ GTK_FILL, GTK_FILL, 0, 0 );
+ ::gtk_widget_show ( label );
+
+ scale = pisa_create_scale ( & ::g_scale_shadow );
+ ::gtk_widget_set_usize ( scale, 200, -1 );
+ ::gtk_scale_set_digits ( GTK_SCALE ( scale ), 0 );
+ ::gtk_range_set_update_policy ( GTK_RANGE ( scale ), GTK_UPDATE_DELAYED );
+ ::gtk_table_attach ( GTK_TABLE ( table ), scale, 1, 2, 2, 3,
+ GTK_FILL, GTK_FILL, 0, 0 );
+ ::gtk_widget_show ( scale );
+
+ // threshold
+ label = ::gtk_label_new ( _( "Threshold:" ) );
+ ::gtk_misc_set_alignment ( GTK_MISC ( label ), 1.0, 0.5 );
+ ::gtk_table_attach ( GTK_TABLE ( table ), label, 0, 1, 3, 4,
+ GTK_FILL, GTK_FILL, 0, 0 );
+ ::gtk_widget_show ( label );
+
+ scale = pisa_create_scale ( & ::g_scale_threshold );
+ ::gtk_widget_set_usize ( scale, 200, -1 );
+ ::gtk_scale_set_digits ( GTK_SCALE ( scale ), 0 );
+ ::gtk_range_set_update_policy ( GTK_RANGE ( scale ), GTK_UPDATE_DELAYED );
+ ::gtk_table_attach ( GTK_TABLE ( table ), scale, 1, 2, 3, 4,
+ GTK_FILL, GTK_FILL, 0, 0 );
+ ::gtk_widget_show ( scale );
+
+ ::gtk_widget_show ( table );
+
+ vbox = gtk_vbox_new (false, 5);
+ gtk_box_pack_start (GTK_BOX (vbox), table, false, false, 0);
+
+ separator = ::gtk_hseparator_new ( );
+ ::gtk_box_pack_start ( GTK_BOX ( vbox ), separator, FALSE, FALSE, 2 );
+ ::gtk_widget_show ( separator );
+
+ // new table
+ table = ::gtk_table_new ( 2, 2, FALSE );
+ ::gtk_container_border_width ( GTK_CONTAINER ( table ), 5 );
+ ::gtk_table_set_row_spacings ( GTK_TABLE ( table ), 3 );
+ ::gtk_table_set_col_spacings ( GTK_TABLE ( table ), 5 );
+
+ // Set brightness-method before you get brightness.
+ scan_mgr->set_brightness_method(br_iscan);
+
+ // brightness
+ label = ::gtk_label_new ( _( "Brightness:" ) );
+ ::gtk_misc_set_alignment ( GTK_MISC ( label ), 1.0, 0.5 );
+ ::gtk_table_attach ( GTK_TABLE ( table ), label, 0, 1, 0, 1,
+ GTK_FILL, GTK_FILL, 0, 0 );
+ ::gtk_widget_show ( label );
+
+ scan_mgr->get_value (SANE_NAME_BRIGHTNESS, &g_scale_brightness.val);
+ scan_mgr->get_range (SANE_NAME_BRIGHTNESS,
+ &g_scale_brightness.max,
+ &g_scale_brightness.min,
+ &g_scale_brightness.step);
+ g_scale_brightness.page = g_scale_brightness.step * 5;
+ brightness_default = g_scale_brightness.val;
+
+ scale = pisa_create_scale ( & ::g_scale_brightness );
+ ::gtk_widget_set_usize ( scale, 200, -1 );
+ ::gtk_scale_set_digits ( GTK_SCALE ( scale ), 0 );
+ ::gtk_range_set_update_policy ( GTK_RANGE ( scale ), GTK_UPDATE_DELAYED );
+ ::gtk_table_attach ( GTK_TABLE ( table ), scale, 1, 2, 0, 1,
+ GTK_FILL, GTK_FILL, 0, 0 );
+ ::gtk_widget_show ( scale );
+
+ // contrast
+ label = ::gtk_label_new ( _( "Contrast:" ) );
+ ::gtk_misc_set_alignment ( GTK_MISC ( label ), 1.0, 0.5 );
+ ::gtk_table_attach ( GTK_TABLE ( table ), label, 0, 1, 1, 2,
+ GTK_FILL, GTK_FILL, 0, 0 );
+ ::gtk_widget_show ( label );
+
+ scan_mgr->get_value (SANE_NAME_CONTRAST, &g_scale_contrast.val);
+ scan_mgr->get_range (SANE_NAME_CONTRAST,
+ &g_scale_contrast.max,
+ &g_scale_contrast.min,
+ &g_scale_contrast.step);
+ g_scale_contrast.page = g_scale_contrast.step * 5;
+ contrast_default = g_scale_contrast.val;
+
+ scale = pisa_create_scale ( & ::g_scale_contrast );
+ ::gtk_widget_set_usize ( scale, 200, -1 );
+ ::gtk_scale_set_digits ( GTK_SCALE ( scale ), 0 );
+ ::gtk_range_set_update_policy ( GTK_RANGE ( scale ), GTK_UPDATE_DELAYED );
+ ::gtk_table_attach ( GTK_TABLE ( table ), scale, 1, 2, 1, 2,
+ GTK_FILL, GTK_FILL, 0, 0 );
+ ::gtk_widget_show ( scale );
+
+ ::gtk_widget_show ( table );
+
+ gtk_box_pack_start (GTK_BOX (vbox), table, false, false, 0);
+
+ return vbox;
+}
diff --git a/frontend/pisa_image_controls.h b/frontend/pisa_image_controls.h
new file mode 100644
index 0000000..89216e7
--- /dev/null
+++ b/frontend/pisa_image_controls.h
@@ -0,0 +1,50 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_IMAGE_CONTROLS_H
+#define ___PISA_IMAGE_CONTROLS_H
+
+#include <gtk/gtk.h>
+
+class image_controls
+{
+ float brightness_default;
+ float contrast_default;
+
+ public:
+
+ // operation
+ void sensitive ( int is_prev_img );
+
+ GtkWidget * create_controls ( void );
+};
+
+#endif // ___PISA_IMAGE_CONTROLS_H
+
diff --git a/frontend/pisa_img_converter.cc b/frontend/pisa_img_converter.cc
new file mode 100644
index 0000000..36e18ec
--- /dev/null
+++ b/frontend/pisa_img_converter.cc
@@ -0,0 +1,99 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 07/10/2001 Peter New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+/*--------------------------------------------------------------*/
+#include "pisa_img_converter.h"
+#include "pisa_error.h"
+
+/*--------------------------------------------------------------*/
+
+
+/*Convert 8bpp grayscale pixels to 24bpp rgb pixels*/
+int convert_grayscale_to_rgb(BYTE* in_buf, long width, long height, BYTE* out_rgb_buf)
+{
+ long i,j,ofs;
+ ofs = 0;
+ for( i = 0; i<height; i++)
+ for( j = 0; j<width; j++, ofs+=3)
+ {
+ /*just copy the gray value 3 times to make rgb*/
+ out_rgb_buf[ofs] = in_buf[i*width+j];
+ out_rgb_buf[ofs+1] = out_rgb_buf[ofs];
+ out_rgb_buf[ofs+2] = out_rgb_buf[ofs];
+ }
+ return PISA_ERR_SUCCESS;
+}
+
+/*Convert 1bpp b&w pixels to 24bpp rgb pixels*/
+int convert_binary_to_rgb(BYTE* in_buf, long width, long height, BYTE* out_rgb_buf)
+{
+ long i,j,in_ofs, out_ofs;
+ int rgb_val;
+ int bitshift;
+ unsigned int temp_byte = 0;
+
+ //offset into input image
+ in_ofs = 0;
+ //offset into output image
+ out_ofs = 0;
+
+ for( i = 0; i<height; i++)
+ {
+ j = 0;
+ bitshift = -1;
+ in_ofs = i*(int)((width+7)/8);
+ while( j<width)
+ {
+ //get a new byte every 8 columns
+ if(j%8 == 0)
+ {
+ temp_byte = in_buf[in_ofs+(int)(j/8)];
+ bitshift = 7;
+ }
+
+ //black or white pixel
+ if((temp_byte >> bitshift) & 1)
+ rgb_val = IMG_CONVERTER_RGB_WHITE;
+ else
+ rgb_val = IMG_CONVERTER_RGB_BLACK;
+
+ //write the rgb values
+ out_rgb_buf[out_ofs] = (unsigned int) rgb_val;
+ out_rgb_buf[out_ofs+1] = (unsigned int)rgb_val;
+ out_rgb_buf[out_ofs+2] = (unsigned int)rgb_val;
+ j++;
+ out_ofs+=3;
+ bitshift--;
+ }
+ }
+ return PISA_ERR_SUCCESS;
+}
+
+
diff --git a/frontend/pisa_img_converter.h b/frontend/pisa_img_converter.h
new file mode 100644
index 0000000..8762bbc
--- /dev/null
+++ b/frontend/pisa_img_converter.h
@@ -0,0 +1,45 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 07/10/2001 Peter New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#include "pisa_enums.h"
+#include <stdio.h>
+
+#ifndef ___PISA_IMG_CONV_H
+#define ___PISA_IMG_CONV_H
+
+typedef unsigned char BYTE;
+
+#define IMG_CONVERTER_RGB_WHITE 255
+#define IMG_CONVERTER_RGB_BLACK 0
+
+int convert_grayscale_to_rgb(BYTE* in_buf, long width, long height, BYTE* out_rgb_buf);
+int convert_binary_to_rgb(BYTE* in_buf, long width, long height, BYTE* out_rgb_buf);
+
+#endif // ___PISA_IMG_CONV_H
diff --git a/frontend/pisa_main.cc b/frontend/pisa_main.cc
new file mode 100644
index 0000000..7cccf7d
--- /dev/null
+++ b/frontend/pisa_main.cc
@@ -0,0 +1,116 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <libintl.h>
+#include <locale.h>
+
+#include "pisa_main.h"
+#include "pisa_gimp.h"
+#include "pisa_view_manager.h"
+#include "pisa_error.h"
+
+int g_gimp_plugin = 1;
+
+/*----------------------------------------------------------*/
+int main ( int argc, char * argv [ ] )
+{
+ int result;
+
+ setlocale ( LC_ALL, "" );
+ bindtextdomain ( PACKAGE, LOCALEDIR );
+#ifdef HAVE_GTK_2
+ bind_textdomain_codeset (PACKAGE, "UTF-8");
+#endif
+ textdomain ( PACKAGE );
+
+ if ( 1 < argc )
+ result = pisa_gimp_main ( argc, argv );
+ else
+ result = 1;
+
+ if ( result )
+ {
+ g_gimp_plugin = 0;
+ pisa_start ( argc, argv );
+ }
+
+ exit ( EXIT_SUCCESS );
+}
+
+/*----------------------------------------------------------*/
+int pisa_gimp_plugin ( void )
+{
+ return g_gimp_plugin;
+}
+
+/*----------------------------------------------------------*/
+void pisa_start ( int argc, char * argv [ ] , int gimpversion)
+{
+ if ( PISA_ERR_SUCCESS != view_manager::create_view_manager ( argc, argv ) )
+ {
+ if ( g_gimp_plugin )
+ pisa_gimp_quit ( );
+ else
+ exit ( EXIT_FAILURE );
+ }
+
+ if ( g_gimp_plugin )
+ {
+#ifdef HAVE_ANY_GIMP
+ gtk_rc_parse ( pisa_gimp_gtkrc ( ) );
+ if (gimpversion == 1)
+ {
+ gdk_set_use_xshm ( pisa_gimp_use_xshm ( ) );
+ }
+ else
+ {
+ gdk_set_use_xshm ( TRUE );
+ }
+#endif // HAVE_ANY_GIMP
+ }
+
+ g_view_manager->create_window ( ID_WINDOW_MAIN );
+ g_view_manager->main ( );
+}
+
+/*----------------------------------------------------------*/
+void pisa_quit ( void )
+{
+ g_view_manager->release_view_manager ( );
+
+ if ( g_gimp_plugin )
+ ::pisa_gimp_quit ( );
+
+ exit ( EXIT_SUCCESS );
+}
+
diff --git a/frontend/pisa_main.h b/frontend/pisa_main.h
new file mode 100644
index 0000000..0860bc1
--- /dev/null
+++ b/frontend/pisa_main.h
@@ -0,0 +1,39 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_MAIN_H
+#define ___PISA_MAIN_H
+
+int pisa_gimp_plugin ( void );
+void pisa_start ( int argc, char * argv [ ], int gimpversion = 0);
+void pisa_quit ( void );
+
+
+#endif // ___PISA_MAIN_H
diff --git a/frontend/pisa_main_window.cc b/frontend/pisa_main_window.cc
new file mode 100644
index 0000000..0aeb86d
--- /dev/null
+++ b/frontend/pisa_main_window.cc
@@ -0,0 +1,1746 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001, 2005, 2008, 2009 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#include <config.h>
+
+#include "gettext.h"
+#define _(msg_id) gettext (msg_id)
+#define N_(msg_id) (msg_id)
+
+/*------------------------------------------------------------*/
+#include <stdio.h>
+#include <string.h>
+#include <cstdlib>
+
+/*------------------------------------------------------------*/
+#include "pisa_main_window.h"
+#include "pisa_error.h"
+#include "pisa_tool.h"
+#include "xpm_data.h"
+#include "pisa_view_manager.h"
+#include "pisa_change_unit.h"
+#include "pisa_default_val.h"
+#include "pisa_scan_selector.h"
+#include "pisa_aleart_dialog.h"
+#include "../lib/pngstream.hh"
+
+#if !GTK_CHECK_VERSION (2,12,0)
+#define gtk_widget_set_tooltip_text(widget,text)
+#endif
+
+/*------------------------------------------------------------*/
+static gint delete_event ( GtkWidget * widget, gpointer data );
+static void click_preview_btn ( void );
+static void click_zoom_btn ( void );
+static void click_expose_btn ( void );
+static void click_scan_btn ( void );
+static void click_scan (GtkWidget *widget, gpointer data);
+static void click_cleaning ( GtkWidget * widget, gpointer data );
+static void click_calibration ( GtkWidget * widget, gpointer data );
+static void click_close ( void );
+
+static void change_destination ( GtkWidget * widget, gpointer data );
+static void change_docsrc ( GtkWidget * widget, gpointer data );
+static void change_imgtype ( GtkWidget * widget, gpointer data );
+static void change_resolution ( GtkWidget * widget, gpointer data );
+static void change_unit ( GtkWidget * widget, gpointer data );
+static void value_changed ( GtkAdjustment * adjust, gpointer data );
+static void toggled_start_button (GtkWidget *widget, gpointer data);
+static void toggled_draft_mode (GtkWidget *widget, gpointer data);
+static void toggled_size_check (GtkWidget *widget, gpointer data);
+static void toggled_deskew (GtkWidget *widget, gpointer data);
+static void change_dfd (GtkWidget *widget, gpointer data);
+static void toggled_usm ( GtkWidget * widget, gpointer data );
+static void check_focus ( GtkWidget * widget, gpointer data );
+static void switch_window_waiting (GtkWidget *widget, gboolean is_waiting, GdkCursor **cursor);
+
+/*------------------------------------------------------------*/
+// destination
+#define ID_MENU_FILE 0x0001
+#define ID_MENU_PRINT 0x0002
+menu_items g_destination_menu_item [ ] =
+{
+ { ID_MENU_FILE, 0, N_("File"), ( GtkSignalFunc * ) change_destination },
+ { ID_MENU_PRINT, 0, N_("Printer"), ( GtkSignalFunc * ) change_destination },
+ { 0, 0, "", 0 }
+};
+
+/*------------------------------------------------------------*/
+// document source
+#define ID_MENU_FLATBED 0x0001
+#define ID_MENU_TPU_NEG 0x0002
+#define ID_MENU_TPU_POS 0x0003
+#define ID_MENU_ADF_SIM 0x0004
+#define ID_MENU_ADF_DUP 0x0005
+menu_items g_docsrc_menu_item [ ] =
+{
+ { ID_MENU_FLATBED, 0, N_("Flatbed"), ( GtkSignalFunc * ) change_docsrc },
+ { ID_MENU_TPU_NEG, 0, N_("TPU - Negative film"), ( GtkSignalFunc * ) change_docsrc },
+ { ID_MENU_TPU_POS, 0, N_("TPU - Positive film"), ( GtkSignalFunc * ) change_docsrc },
+ { ID_MENU_ADF_SIM, 0, N_("ADF - Single-sided"), ( GtkSignalFunc * ) change_docsrc },
+ { ID_MENU_ADF_DUP, 0, N_("ADF - Double-sided"), ( GtkSignalFunc * ) change_docsrc },
+ { 0, 0, "", 0 }
+};
+
+/*------------------------------------------------------------*/
+// image type
+#define ID_MENU_24C_PHOTO 0x0001
+#define ID_MENU_24C_DOC 0x0002
+#define ID_MENU_8G_PHOTO 0x0003
+#define ID_MENU_8G_DOC 0x0004
+#define ID_MENU_LINEART 0x0005
+menu_items g_imgtype_menu_item [ ] =
+{
+ { ID_MENU_24C_PHOTO, 0, N_("Color Photo"), ( GtkSignalFunc * ) change_imgtype },
+ { ID_MENU_24C_DOC, 0, N_("Color Document"), ( GtkSignalFunc * ) change_imgtype },
+ { ID_MENU_8G_PHOTO, 0, N_("Black & White Photo"), ( GtkSignalFunc * ) change_imgtype },
+ { ID_MENU_8G_DOC, 0, N_("Black & White Document"), ( GtkSignalFunc * ) change_imgtype },
+ { ID_MENU_LINEART, 0, N_("Line Art"), ( GtkSignalFunc * ) change_imgtype },
+ { 0, 0, "", 0 }
+};
+
+imagetype g_imagetype_list [ ] =
+{
+ { ID_MENU_24C_PHOTO, PISA_PT_RGB, PISA_BD_8, PISA_DESCREEN_OFF, PISA_AE_PHOTO, PISA_DO_NONE, PISA_MO_NONE, PISA_HT_NONE },
+ { ID_MENU_24C_DOC, PISA_PT_RGB, PISA_BD_8, PISA_DESCREEN_ON, PISA_AE_DOC, PISA_DO_NONE, PISA_MO_NONE, PISA_HT_NONE },
+ { ID_MENU_8G_PHOTO, PISA_PT_GRAY, PISA_BD_8, PISA_DESCREEN_OFF, PISA_AE_PHOTO, PISA_DO_NONE, PISA_MO_NONE, PISA_HT_NONE },
+ { ID_MENU_8G_DOC, PISA_PT_GRAY, PISA_BD_8, PISA_DESCREEN_ON, PISA_AE_DOC, PISA_DO_NONE, PISA_MO_NONE, PISA_HT_NONE },
+ { ID_MENU_LINEART, PISA_PT_BW, PISA_BD_1, PISA_DESCREEN_OFF, PISA_AE_GRAYED, PISA_DO_NONE, PISA_MO_NONE, PISA_HT_NONE }
+};
+
+/*------------------------------------------------------------*/
+// resolution
+menu_items g_resolution_menu_item [ ] =
+{
+ { 50, 0, "50dpi", ( GtkSignalFunc * ) change_resolution },
+ { 72, 0, "72dpi", ( GtkSignalFunc * ) change_resolution },
+ { 96, 0, "96dpi", ( GtkSignalFunc * ) change_resolution },
+ { 150, 0, "150dpi", ( GtkSignalFunc * ) change_resolution },
+ { 200, 0, "200dpi", ( GtkSignalFunc * ) change_resolution },
+ { 240, 0, "240dpi", ( GtkSignalFunc * ) change_resolution },
+ { 300, 0, "300dpi", ( GtkSignalFunc * ) change_resolution },
+ { 360, 0, "360dpi", ( GtkSignalFunc * ) change_resolution },
+ { 400, 0, "400dpi", ( GtkSignalFunc * ) change_resolution },
+ { 600, 0, "600dpi", ( GtkSignalFunc * ) change_resolution },
+ { 720, 0, "720dpi", ( GtkSignalFunc * ) change_resolution },
+ { 800, 0, "800dpi", ( GtkSignalFunc * ) change_resolution },
+ { 1200, 0, "1200dpi", ( GtkSignalFunc * ) change_resolution },
+ { 1600, 0, "1600dpi", ( GtkSignalFunc * ) change_resolution },
+ { 2400, 0, "2400dpi", ( GtkSignalFunc * ) change_resolution },
+ { 3200, 0, "3200dpi", ( GtkSignalFunc * ) change_resolution },
+ { 4800, 0, "4800dpi", ( GtkSignalFunc * ) change_resolution },
+ { 6400, 0, "6400dpi", ( GtkSignalFunc * ) change_resolution },
+ { 9600, 0, "9600dpi", ( GtkSignalFunc * ) change_resolution },
+ {12800, 0,"12800dpi", ( GtkSignalFunc * ) change_resolution },
+ { 0, 0, "", 0 }
+};
+
+/*------------------------------------------------------------*/
+// unit
+menu_items g_unit_menu_item [ ] =
+{
+ { PISA_UNIT_INCHES, 0, N_("inches"), ( GtkSignalFunc * ) change_unit },
+ { PISA_UNIT_PIXELS, 0, N_("pixels"), ( GtkSignalFunc * ) change_unit },
+ { PISA_UNIT_CM , 0, N_("cm"), ( GtkSignalFunc * ) change_unit },
+ { 0, 0, "", 0 }
+};
+
+/*------------------------------------------------------------*/
+// scale
+#define ID_SCALE_ZOOM 0x0001
+
+scale_items g_scale_zoom = { ID_SCALE_ZOOM, 0, 0,
+ ( GtkSignalFunc * ) value_changed,
+ 100, 50, 200, 1, 10 };
+
+/*------------------------------------------------------------*/
+// focus
+
+long g_focus [ ] = { 0, 25 };
+
+/*------------------------------------------------------------*/
+// double feed detection
+menu_items g_double_feed_detection[] =
+{
+ { PISA_DFD_OFF, 0, N_("Off"), (GtkSignalFunc *) change_dfd },
+ { PISA_DFD_STD, 0, N_("Standard"), (GtkSignalFunc *) change_dfd },
+ { PISA_DFD_THIN, 0, N_("Thin"), (GtkSignalFunc *) change_dfd },
+ { 0, 0, "", 0 }
+};
+
+static void
+change_dfd (GtkWidget *widget, gpointer data)
+{
+ scan_manager *sm = g_view_manager->get_scan_manager ();
+ if (sm && sm->has_dfd ()) sm->set_dfd (*(long *) data);
+}
+
+/*------------------------------------------------------------*/
+static gint delete_event ( GtkWidget * widget, gpointer data )
+{
+ widget = widget;
+ data = data;
+
+ ::g_view_manager->close_window ( ID_WINDOW_MAIN, 0 );
+
+ return FALSE;
+}
+
+/*------------------------------------------------------------*/
+void main_window::start_preview ( bool zooming )
+{
+ preview_window * prev_cls;
+ main_window * main_cls;
+
+ prev_cls = ( preview_window * ) ::g_view_manager->get_window_cls ( ID_WINDOW_PREV );
+
+ main_cls = ( main_window * ) ::g_view_manager->get_window_cls ( ID_WINDOW_MAIN );
+
+ scan_manager *sm = g_view_manager->get_scan_manager ();
+
+ if (sm->has_preview ()) sm->set_preview (true);
+ main_cls->do_sensitive_size_check (false);
+
+ if (zooming)
+ {
+ prev_cls->start_zoom ();
+ }
+ else
+ {
+ prev_cls->start_preview ();
+ }
+
+ main_cls->do_sensitive_size_check (true);
+ if (sm->has_preview ()) sm->set_preview (false);
+}
+
+/*------------------------------------------------------------*/
+static void click_preview_btn ( void )
+{
+ main_window *main_cls = ( main_window * ) ::g_view_manager->get_window_cls ( ID_WINDOW_MAIN );
+ main_cls->start_preview (false);
+}
+
+/*------------------------------------------------------------*/
+static void click_zoom_btn ( void )
+{
+ main_window *main_cls = ( main_window * ) ::g_view_manager->get_window_cls ( ID_WINDOW_MAIN );
+ main_cls->start_preview (true);
+}
+
+/*------------------------------------------------------------*/
+static void click_expose_btn ( void )
+{
+ preview_window * prev_cls;
+
+ prev_cls = ( preview_window * ) ::g_view_manager->get_window_cls ( ID_WINDOW_PREV );
+
+ prev_cls->auto_exposure ( );
+ prev_cls->update_img ( );
+}
+
+/*------------------------------------------------------------*/
+static void click_scan_btn ( void )
+{
+ ::g_view_manager->start_scan ( );
+}
+
+static void
+click_scan (GtkWidget *destination, gpointer data)
+{
+ main_window *mw = static_cast <main_window *> (data);
+ mw->start_scan (destination);
+}
+
+static void
+click_calibration ( GtkWidget * widget, gpointer data )
+{
+ scan_manager *scan_mgr = static_cast< scan_manager * > (data);
+ GdkCursor *cur = NULL;
+
+ if (scan_mgr->has_calibrate ())
+ {
+ switch_window_waiting (widget, true, &cur);
+
+ try
+ {
+ scan_mgr->calibrate ();
+ }
+ catch (pisa_error & err)
+ {
+ aleart_dialog aleart_dlg;
+ aleart_dlg.message_box (0, _("Calibration is failed."));
+ }
+
+ switch_window_waiting (widget, false, &cur);
+ }
+
+ return;
+}
+
+static void
+click_cleaning ( GtkWidget * widget, gpointer data )
+{
+ scan_manager *scan_mgr = static_cast< scan_manager * > (data);
+ GdkCursor *cur = NULL;
+
+ if (scan_mgr->has_clean ())
+ {
+ switch_window_waiting (widget, true, &cur);
+
+ try
+ {
+ scan_mgr->clean ();
+ }
+ catch (pisa_error & err)
+ {
+ aleart_dialog aleart_dlg;
+ aleart_dlg.message_box (0, _("Cleaning is failed."));
+ }
+
+ switch_window_waiting (widget, false, &cur);
+ }
+
+ return;
+}
+
+/*------------------------------------------------------------*/
+static void click_close ( void )
+{
+ ::g_view_manager->close_window ( ID_WINDOW_MAIN, 1 );
+}
+
+/*------------------------------------------------------------*/
+static void click_config ( void )
+{
+ ::g_view_manager->create_window ( ID_WINDOW_CONFIG );
+}
+
+/*------------------------------------------------------------*/
+static void change_destination ( GtkWidget * widget, gpointer data )
+{
+ char dest;
+ long id;
+
+ widget = widget;
+ id = * ( long * ) data;
+
+ switch ( id )
+ {
+ case ID_MENU_FILE:
+ dest = PISA_DE_FILE;
+ break;
+
+ case ID_MENU_PRINT:
+ dest = PISA_DE_PRINTER;
+ break;
+
+ default:
+ return;
+ }
+
+ g_view_manager->set_destination (dest);
+}
+
+/*------------------------------------------------------------*/
+static void change_docsrc ( GtkWidget * widget, gpointer data )
+{
+ widget = widget;
+
+ // ASSUMPTION: flatbed is the default at program startup
+ static long current_id = ID_MENU_FLATBED;
+
+ long id = *(long *) data;
+ if (current_id == id) return; // no change
+
+ scan_manager *sm = g_view_manager->get_scan_manager ();
+
+ switch (id)
+ {
+ case ID_MENU_FLATBED:
+ sm->select_flatbed ();
+ break;
+ case ID_MENU_TPU_NEG:
+ case ID_MENU_TPU_POS:
+ sm->select_tpu (ID_MENU_TPU_POS == id);
+ break;
+ case ID_MENU_ADF_SIM:
+ case ID_MENU_ADF_DUP:
+ sm->select_adf (ID_MENU_ADF_DUP == id);
+ break;
+ default:
+ return;
+ }
+ current_id = id;
+
+ if (!g_view_manager->change_document_source (g_imagetype_list))
+ { // reset drop down menu to default
+ gtk_menu_set_active
+ (GTK_MENU (gtk_widget_get_ancestor (widget, GTK_TYPE_MENU)), 0);
+ }
+
+ main_window *mw = (static_cast <main_window *>
+ (g_view_manager->get_window_cls (ID_WINDOW_MAIN)));
+ if (mw->get_widget (WG_MAIN_DFD))
+ gtk_widget_set_sensitive (mw->get_widget (WG_MAIN_DFD), sm->using_adf ());
+}
+
+/*------------------------------------------------------------*/
+static void change_imgtype ( GtkWidget * widget, gpointer data )
+{
+ unsigned i;
+ long id;
+
+ widget = widget;
+ id = * ( long * ) data;
+
+ for ( i = 0; i < sizeof ( ::g_imagetype_list ) /
+ sizeof ( ::g_imagetype_list [ 0 ] ); i++ )
+ {
+ if ( id == g_imagetype_list [ i ].id )
+ {
+ preview_window * prev_cls;
+
+ g_view_manager->set_image_type (&g_imagetype_list[i]);
+ g_view_manager->sensitive ();
+
+ prev_cls = ( preview_window * ) ::g_view_manager->get_window_cls ( ID_WINDOW_PREV );
+
+ prev_cls->auto_exposure ( );
+
+ ::g_view_manager->update_lut ( );
+
+ prev_cls->update_img ( );
+
+ return;
+ }
+ }
+}
+
+/*------------------------------------------------------------*/
+static void change_resolution ( GtkWidget * widget, gpointer data )
+{
+ widget = widget;
+
+ g_view_manager->set_resolution (* (long *) data);
+
+ ::g_view_manager->sensitive ( );
+}
+
+/*------------------------------------------------------------*/
+static void change_unit ( GtkWidget * widget, gpointer data )
+{
+ widget = widget;
+
+ g_view_manager->set_unit (* (long *) data);
+
+ ::g_view_manager->sensitive ( );
+}
+
+/*------------------------------------------------------------*/
+static void value_changed ( GtkAdjustment * adjust, gpointer data )
+{
+ long id;
+ long val;
+ int change;
+ marquee * marq;
+
+ id = * ( long * ) data;
+ change = 1;
+ marq = & g_view_manager->get_marquee ();
+
+ switch ( id )
+ {
+ case ID_SCALE_ZOOM:
+ val = ( long ) ( adjust->value );
+ if ( marq->scale == val )
+ change = 0;
+ else
+ marq->scale = val;
+ break;
+ }
+
+ if ( change )
+ {
+ ::g_view_manager->sensitive ( );
+ }
+}
+
+static void
+toggled_start_button (GtkWidget *widget, gpointer data)
+{
+ data = data;
+
+ g_view_manager->enable_start_button (gtk_toggle_button_get_active
+ (GTK_TOGGLE_BUTTON (widget)));
+}
+
+static void
+toggled_draft_mode (GtkWidget *widget, gpointer data)
+{
+ data = data;
+
+ g_view_manager->enable_draft_mode (gtk_toggle_button_get_active
+ (GTK_TOGGLE_BUTTON (widget)));
+}
+
+static void
+toggled_size_check (GtkWidget *widget, gpointer data)
+{
+ main_window *mw = (main_window *) data;
+ scan_manager *sm = g_view_manager->get_scan_manager ();
+ mw->update_zoom_button ();
+
+ gboolean is_active =
+ gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
+ if ( sm->has_size_check () )
+ {
+ sm->set_size_check (is_active);
+ }
+ if ( sm->has_autocrop () )
+ {
+ sm->set_autocrop (is_active);
+ }
+ mw->sensitive_option();
+}
+
+static void
+toggled_deskew (GtkWidget *widget, gpointer data)
+{
+ main_window *mw = (main_window *) data;
+
+ g_view_manager
+ ->get_scan_manager()
+ ->set_deskew (gtk_toggle_button_get_active
+ (GTK_TOGGLE_BUTTON (widget)));
+ mw->sensitive_option();
+}
+
+/*------------------------------------------------------------*/
+static void toggled_usm ( GtkWidget * widget, gpointer data )
+{
+ data = data;
+
+ if ( ::gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON ( widget ) ) )
+ g_view_manager->enable_unsharp_mask (true);
+ else
+ g_view_manager->enable_unsharp_mask (false);
+
+ preview_window *prev_cls =
+ (preview_window *) ::g_view_manager->get_window_cls (ID_WINDOW_PREV);
+
+ prev_cls->update_img ( );
+}
+
+/*------------------------------------------------------------*/
+static void check_focus ( GtkWidget * widget, gpointer data )
+{
+ long id;
+ marquee * marq;
+
+ widget = widget;
+
+ marq = & g_view_manager->get_marquee ();
+ id = * ( long * ) data;
+
+ if ( id == g_focus [ 1 ] )
+ marq->focus = 25;
+ else
+ marq->focus = 0;
+}
+
+/*------------------------------------------------------------*/
+static void
+switch_window_waiting (GtkWidget *widget, gboolean is_waiting, GdkCursor **cur)
+{
+ GtkWidget *main_win = ::gtk_widget_get_toplevel (widget);
+ GdkWindow *window = main_win->window;
+
+ if (is_waiting)
+ {
+ ::gtk_widget_set_sensitive (main_win, false);
+
+ *cur = ::gdk_cursor_new (GDK_WATCH);
+ ::gdk_window_set_cursor (window, *cur);
+ }
+ else
+ {
+ ::gdk_window_set_cursor (window, NULL);
+ ::gdk_cursor_unref (*cur);
+
+ ::gtk_widget_set_sensitive (main_win, true);
+ }
+
+ while (::gtk_events_pending ())
+ ::gtk_main_iteration ();
+}
+
+/*------------------------------------------------------------*/
+int main_window::init ( void )
+{
+ unsigned int i;
+
+ for ( i = 0; i < WG_MAIN_NUM; i++ )
+ m_widget [ i ] = 0;
+
+ g_view_manager->set_image_type (&g_imagetype_list[0]);
+
+ _done_preview = false;
+ _do_sensitive_size_check = true;
+
+ return PISA_ERR_SUCCESS;
+}
+
+
+/*------------------------------------------------------------*/
+GtkWidget * main_window::create_window ( GtkWidget * parent )
+{
+ GtkWidget * top;
+ GtkWidget * hbox;
+ GtkWidget * left_area, * right_area;
+
+ parent = parent;
+
+ // determine GUI mode
+ _compact_mode = false;
+ gint padding = 10;
+ GdkScreen * screen = gdk_screen_get_default ();
+ if ((screen && 768 > gdk_screen_get_height (screen))
+ || getenv ("ISCAN_COMPACT_GUI"))
+ {
+ padding = 2;
+ _compact_mode = true;
+ if (getenv ("ISCAN_DEBUG"))
+ fprintf (stderr, "Compact GUI mode enabled.\n");
+ }
+ else
+ if (getenv ("ISCAN_DEBUG"))
+ {
+ if (!screen) fprintf (stderr, "No default screen found. ");
+ fprintf (stderr, "Standard GUI mode enabled.\n");
+ }
+
+
+ // main window
+ top = m_widget [ WG_MAIN_TOP ] = ::gtk_window_new ( GTK_WINDOW_TOPLEVEL );
+ ::gtk_container_border_width ( GTK_CONTAINER ( top ), padding );
+ ::gtk_window_set_title ( GTK_WINDOW ( top ), PACKAGE_STRING );
+ ::gtk_widget_set_uposition ( top, POS_MAIN_X, POS_MAIN_Y );
+
+ ::gtk_signal_connect ( GTK_OBJECT ( top ), "delete_event",
+ GTK_SIGNAL_FUNC ( ::delete_event ), 0 );
+
+ hbox = ::gtk_hbox_new ( FALSE, 5 );
+ ::gtk_container_add ( GTK_CONTAINER ( top ), hbox );
+ ::gtk_widget_show ( hbox );
+
+ // The icons for our buttons rely on the main widget's top level to
+ // be realized. These buttons were created in `create_left_area()`
+ // in the past but as we are heavily rearranging parts of the GUI,
+ // we'd better make sure it's done early.
+ ::gtk_widget_realize ( m_widget [ WG_MAIN_TOP ] );
+
+ create_controls ();
+
+ left_area = create_left_area ( );
+ ::gtk_box_pack_start ( GTK_BOX ( hbox ), left_area, TRUE, TRUE, 0 );
+ ::gtk_widget_show ( left_area );
+
+ right_area = create_right_area ( );
+ ::gtk_box_pack_start ( GTK_BOX ( hbox ), right_area, FALSE, FALSE, 0 );
+ ::gtk_widget_show ( right_area );
+
+ ::gtk_widget_show ( top );
+
+ gtk_widget_grab_focus (prev_btn_);
+
+ ::g_view_manager->sensitive ( );
+
+ return top;
+}
+
+/*------------------------------------------------------------*/
+int main_window::close_window ( int destroy )
+{
+ ::g_view_manager->close_window ( ID_WINDOW_PREV, 1 );
+
+ if ( destroy && m_widget [ WG_MAIN_TOP ] )
+ ::gtk_widget_destroy ( m_widget [ WG_MAIN_TOP ] );
+
+ m_widget [ WG_MAIN_TOP ] = 0;
+
+ return PISA_ERR_SUCCESS;
+}
+
+void
+main_window::start_scan (const GtkWidget *destination) const
+{
+ if (destination == scan_btn_)
+ g_view_manager->set_destination (PISA_DE_FILE);
+ else if (destination == print_btn_)
+ g_view_manager->set_destination (PISA_DE_PRINTER);
+ else {
+ fprintf (stderr, "Unsupported scan destination.\n");
+ return;
+ }
+ g_view_manager->start_scan ();
+}
+
+void
+main_window::set_double_feed_detection (const GtkWidget *widget)
+{
+ long value = PISA_DFD_OFF;
+
+ if (widget == m_widget[WG_MAIN_DFD]) {
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
+ gtk_widget_set_sensitive (m_widget[WG_MAIN_DFD_BOX], true);
+ if (gtk_toggle_button_get_active
+ (GTK_TOGGLE_BUTTON (m_widget[WG_MAIN_DFD_STD]))) {
+ value = PISA_DFD_STD;
+ } else if (gtk_toggle_button_get_active
+ (GTK_TOGGLE_BUTTON (m_widget[WG_MAIN_DFD_THIN]))) {
+ value = PISA_DFD_THIN;
+ } else {
+ fprintf (stderr, "No known active DFD value. "
+ "Falling back to 'Standard'.\n");
+ value = PISA_DFD_STD;
+ }
+ } else {
+ gtk_widget_set_sensitive (m_widget[WG_MAIN_DFD_BOX], false);
+ value = PISA_DFD_OFF;
+ }
+ } else {
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
+ if (widget == m_widget[WG_MAIN_DFD_STD]) {
+ value = PISA_DFD_STD;
+ } else if (widget == m_widget[WG_MAIN_DFD_THIN]) {
+ value = PISA_DFD_THIN;
+ } else {
+ fprintf (stderr, "Unsupported DFD value.\n");
+ return;
+ }
+ } else {
+ return; // nothing to do
+ }
+ }
+
+ scan_manager *sm = g_view_manager->get_scan_manager ();
+ if (sm && sm->has_dfd ()) sm->set_dfd (value);
+}
+
+/*------------------------------------------------------------*/
+void main_window::sensitive ( int is_prev_img )
+{
+ gboolean enable_expose, enable_dest, enable_config;
+
+ _done_preview = bool (is_prev_img);
+
+ enable_expose = enable_dest = enable_config= TRUE;
+
+ if ( is_prev_img == 0 ||
+ g_view_manager->get_settings ().imgtype.pixeltype == PISA_PT_BW )
+ enable_expose = FALSE;
+
+ if ( ::g_view_manager->is_gimp ( ) )
+ {
+ enable_dest = FALSE;
+ enable_config = FALSE;
+ }
+
+ update_zoom_button ();
+
+ gtk_widget_set_sensitive (expo_btn_, enable_expose);
+ gtk_widget_set_sensitive ((_compact_mode ? print_btn_ : dest_menu_),
+ enable_dest);
+ gtk_widget_set_sensitive (config_, enable_config);
+
+ sensitive_option ();
+}
+
+void main_window::sensitive_option ()
+{
+ sensitive_image_type ( );
+ sensitive_scale ( );
+ sensitive_resolution ( );
+ sensitive_target ( );
+ sensitive_focus ( );
+ sensitive_usm ( );
+ sensitive_size_check ();
+ sensitive_deskew ();
+}
+
+void
+main_window::enable_start_button (bool yes)
+{
+ gtk_widget_set_sensitive (scan_navi_, yes);
+}
+
+void
+main_window::update_zoom_button ()
+{
+ long marq_size = 0;
+ gboolean size_check_enabled = FALSE;
+ gboolean enable_zoom = TRUE;
+
+ marq_size = g_view_manager->get_marquee_size ();
+ size_check_enabled = gtk_toggle_button_get_active
+ (GTK_TOGGLE_BUTTON (size_check_));
+
+ if (marq_size < 2 || size_check_enabled || !_done_preview)
+ {
+ enable_zoom = FALSE;
+ }
+
+ gtk_widget_set_sensitive (zoom_btn_, enable_zoom);
+}
+
+static GtkWidget *
+create_scan_button_(main_window *mw, bool toolbar)
+{
+ GtkWidget *button;
+
+ if (toolbar) {
+ button = GTK_WIDGET (gtk_tool_button_new_from_stock (GTK_STOCK_SAVE_AS));
+ gtk_widget_set_tooltip_text (button, _("Scan to File"));
+ gtk_signal_connect (GTK_OBJECT (button), "clicked",
+ GTK_SIGNAL_FUNC (click_scan), mw);
+ } else {
+ button = gtk_button_new ();
+ gtk_container_add (GTK_CONTAINER (button),
+ xpmlabelbox (mw->get_widget (), scan_xpm, _("Scan")));
+ gtk_signal_connect (GTK_OBJECT (button), "clicked",
+ GTK_SIGNAL_FUNC (click_scan_btn), mw);
+ }
+
+ return button;
+}
+
+static GtkWidget *
+create_pref_button_(main_window *mw, bool toolbar)
+{
+ GtkWidget *button;
+ const char *text = _("Configuration");
+
+ if (toolbar) {
+ button = GTK_WIDGET (gtk_tool_button_new_from_stock
+ (GTK_STOCK_PREFERENCES));
+ gtk_widget_set_tooltip_text (button, text);
+ } else {
+ button = gtk_button_new_with_label (text);
+ }
+ gtk_signal_connect (GTK_OBJECT (button), "clicked",
+ GTK_SIGNAL_FUNC (click_config), mw);
+
+ return button;
+}
+
+//! A constructor-like tool button builder.
+GtkWidget *
+main_window::tool_button (const char *text, GCallback callback, gchar **icon)
+{
+ GtkWidget *widget;
+
+ if (_compact_mode) {
+ widget = GTK_WIDGET (gtk_tool_button_new
+ (xpm2widget (get_widget (), icon), text));
+ gtk_widget_set_tooltip_text (widget, text);
+ } else {
+ widget = gtk_button_new ();
+ gtk_container_add (GTK_CONTAINER (widget),
+ xpmlabelbox (get_widget (), icon, text));
+ }
+ g_signal_connect (G_OBJECT (widget), "clicked", callback, this);
+
+ return widget;
+}
+
+//! A constructor-like check button builder.
+GtkWidget *
+main_window::check_button (const char *text,
+ void (*callback) (GtkWidget *, gpointer))
+{
+ GtkWidget *widget;
+
+ widget = gtk_check_button_new_with_label (text);
+ g_signal_connect (G_OBJECT (widget), "toggled",
+ G_CALLBACK (callback), this);
+
+ return widget;
+}
+
+//! A constructor-like close button builder.
+GtkWidget *
+main_window::close_button ()
+{
+ GtkWidget *widget;
+ const char *text = _("Close");
+
+ if (_compact_mode) {
+ widget = GTK_WIDGET (gtk_tool_button_new_from_stock (GTK_STOCK_CLOSE));
+ gtk_widget_set_tooltip_text (widget, text);
+ } else {
+ widget = gtk_button_new_with_label (text);
+ }
+ g_signal_connect (G_OBJECT (widget), "clicked",
+ G_CALLBACK (click_close), this);
+
+ return widget;
+}
+
+void
+main_window::create_controls ()
+{
+ scan_manager *sm = g_view_manager->get_scan_manager ();
+
+ prev_btn_ = tool_button (_("Preview"), click_preview_btn, preview_xpm);
+ zoom_btn_ = tool_button (_("Zoom"), click_zoom_btn, zoom_xpm);
+ expo_btn_ = tool_button (_("Auto Exposure"), click_expose_btn, auto_xpm);
+ scan_btn_ = create_scan_button_(this, _compact_mode);
+
+ scan_navi_ = check_button (_("enable Start button"), toggled_start_button);
+ draft_mode_ = check_button (_("Speed priority scanning"),
+ toggled_draft_mode);
+ size_check_ = check_button (_("Document Size - Auto Detect"),
+ toggled_size_check);
+ deskew_ = check_button (_("Correct Document Skew"),
+ toggled_deskew);
+
+ config_ = create_pref_button_(this, _compact_mode);
+
+ unsharp_mask_ = check_button (_("Unsharp mask"), toggled_usm);
+
+ if (getenv ("ISCAN_GUI_SHOW_ALL") || sm->has_dfd ()) {
+ GtkWidget *widget;
+ const char *text = _("Detect Double Feed (Paper Thickness)");
+ GtkWidget *menu = pisa_create_option_menu (g_double_feed_detection);
+ GtkWidget *label = gtk_label_new (text);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+
+ widget = gtk_hbox_new (false, 5);
+ gtk_box_pack_start (GTK_BOX (widget), label, false, false, 5);
+ gtk_box_pack_start (GTK_BOX (widget), menu, false, false, 5);
+ m_widget[WG_MAIN_DFD] = widget;
+ gtk_widget_set_sensitive (m_widget[WG_MAIN_DFD], sm->using_adf ());
+ }
+
+ if (sm->push_button_needs_polling ())
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (scan_navi_), true);
+ gtk_widget_set_sensitive (scan_navi_, false);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (scan_navi_, sm->has_flatbed () || sm->has_tpu ());
+ }
+}
+
+/*------------------------------------------------------------*/
+GtkWidget * main_window::create_left_area ( void )
+{
+ GtkWidget *vbox = gtk_vbox_new (false, 0);
+
+ if (!_compact_mode)
+ gtk_box_pack_start (GTK_BOX (vbox), action_button_box (), false, false, 0);
+
+ GtkWidget *widget = g_view_manager->create_window (ID_WINDOW_PREV);
+ // In order to avoid a segmentation error in the preview_window
+ // class (resulting from sloppy image size logic), we add a size
+ // kludge here so that the conditions that trigger it become very
+ // unlikely.
+ // FIXME: remove kludge once preview_window gets fixed.
+ ::gtk_widget_set_usize (GTK_WIDGET (widget), 360, 480);
+ ::gtk_box_pack_start ( GTK_BOX ( vbox ), widget, TRUE, TRUE, 0 );
+ ::gtk_widget_show ( widget );
+
+ // destination menu and scan button
+ if (!_compact_mode)
+ {
+ widget = create_scan_button ( );
+ ::gtk_box_pack_end ( GTK_BOX ( vbox ), widget, FALSE, FALSE, 0 );
+ ::gtk_widget_show ( widget );
+ }
+
+ return vbox;
+}
+
+//! Assembles a widget with selected "action" buttons.
+/*! \note Intended for use with the non-compact GUI mode only.
+ */
+GtkWidget *
+main_window::action_button_box ()
+{
+ if (_compact_mode) return NULL;
+
+ GtkWidget *hbox = gtk_hbox_new (false, 5);
+
+ gtk_box_pack_start (GTK_BOX (hbox), prev_btn_, false, false, 5);
+ gtk_box_pack_start (GTK_BOX (hbox), zoom_btn_, false, false, 5);
+ gtk_box_pack_start (GTK_BOX (hbox), expo_btn_, false, false, 5);
+ gtk_widget_show_all (hbox);
+
+ return hbox;
+}
+
+//! Assembles a toolbar widget from selected controls.
+/*! \note Intended for use in compact GUI mode only.
+ */
+GtkWidget *
+main_window::toolbar ()
+{
+ if (!_compact_mode) return NULL;
+
+ const gint at_end = -1;
+ GtkToolbar *toolbar = GTK_TOOLBAR (gtk_toolbar_new ());
+ gtk_toolbar_set_style (toolbar, GTK_TOOLBAR_ICONS);
+ gtk_toolbar_set_show_arrow (toolbar, false);
+
+ GtkToolItem *btn, *sep;
+
+ btn = gtk_tool_button_new_from_stock (GTK_STOCK_PRINT);
+ gtk_widget_set_tooltip_text (GTK_WIDGET (btn), _("Scan to Print"));
+ gtk_signal_connect (GTK_OBJECT (btn), "clicked",
+ GTK_SIGNAL_FUNC (click_scan), this);
+ print_btn_ = GTK_WIDGET (btn);
+
+ // Spring separator to push remaining buttons to the far end of
+ // the toolbar
+ sep = gtk_separator_tool_item_new ();
+ gtk_tool_item_set_expand (sep, true);
+ gtk_separator_tool_item_set_draw (GTK_SEPARATOR_TOOL_ITEM (sep),
+ false);
+
+ // Final toolbar tweaks
+ if (!iscan::pngstream::is_usable ()) {
+ gtk_widget_set_sensitive (print_btn_, false);
+ }
+
+ gtk_toolbar_insert (toolbar, GTK_TOOL_ITEM (prev_btn_), at_end);
+ gtk_toolbar_insert (toolbar, GTK_TOOL_ITEM (zoom_btn_), at_end);
+ gtk_toolbar_insert (toolbar, GTK_TOOL_ITEM (expo_btn_), at_end);
+ gtk_toolbar_insert (toolbar, gtk_separator_tool_item_new (), at_end);
+ gtk_toolbar_insert (toolbar, GTK_TOOL_ITEM (scan_btn_), at_end);
+ gtk_toolbar_insert (toolbar, GTK_TOOL_ITEM (print_btn_), at_end);
+ gtk_toolbar_insert (toolbar, gtk_separator_tool_item_new (), at_end);
+ gtk_toolbar_insert (toolbar, GTK_TOOL_ITEM (config_), at_end);
+ gtk_toolbar_insert (toolbar, sep, at_end);
+ gtk_toolbar_insert (toolbar, GTK_TOOL_ITEM (close_button ()), at_end);
+
+ gtk_widget_show_all (GTK_WIDGET (toolbar));
+
+ return GTK_WIDGET (toolbar);
+}
+
+/*------------------------------------------------------------*/
+GtkWidget * main_window::create_scan_button ( void )
+{
+ GtkWidget * table;
+ GtkWidget * label;
+ GtkWidget * menu;
+ GtkWidget * vbox;
+
+ table = ::gtk_table_new ( 2, 3, FALSE );
+ ::gtk_container_border_width ( GTK_CONTAINER ( table ), 5 );
+ ::gtk_table_set_row_spacings ( GTK_TABLE ( table ), 3 );
+ ::gtk_table_set_col_spacings ( GTK_TABLE ( table ), 8 );
+
+ label = ::gtk_label_new ( _( "Destination:" ) );
+ ::gtk_table_attach ( GTK_TABLE ( table ), label, 0, 1, 0, 1,
+ GTK_FILL, GTK_FILL, 0, 0 );
+ ::gtk_widget_show ( label );
+
+ menu = ::pisa_create_option_menu ( g_destination_menu_item );
+ ::gtk_table_attach ( GTK_TABLE ( table ), menu, 1, 2, 0, 1,
+ GTK_FILL, GTK_FILL, 0, 0 );
+ if (!iscan::pngstream::is_usable ()) {
+ gtk_widget_set_sensitive (g_destination_menu_item[1].widget, FALSE);
+ }
+ ::gtk_widget_show ( menu );
+ dest_menu_ = menu;
+
+ gtk_table_attach (GTK_TABLE (table), scan_btn_,
+ 2, 3, 0, 2, GTK_FILL, GTK_FILL, 0, 0);
+ gtk_widget_show_all (scan_btn_);
+
+ vbox = gtk_vbox_new ( FALSE, 0 );
+ gtk_table_attach ( GTK_TABLE ( table ), vbox, 0, 2, 1, 2,
+ GTK_FILL, GTK_FILL, 0, 0 );
+ gtk_widget_show ( vbox );
+
+ gtk_box_pack_start (GTK_BOX (vbox), scan_navi_ , false, false, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), draft_mode_, false, false, 0);
+
+ scan_manager *sm = g_view_manager->get_scan_manager ();
+ if (sm->has_start_button ())
+ gtk_widget_show (scan_navi_);
+ if (sm->has_draft_mode ())
+ gtk_widget_show (draft_mode_);
+
+ gtk_widget_show (table);
+ return table;
+}
+
+/*------------------------------------------------------------*/
+GtkWidget * main_window::create_right_area ( void )
+{
+ GtkWidget * vbox;
+ GtkWidget * widget;
+
+ vbox = ::gtk_vbox_new ( FALSE, 5 );
+
+ if (!_compact_mode)
+ {
+ #if THE_ORIGINAL_SOURCES_WERE_NOT_SUCH_A_MESS
+ scan_selector *ss = new scan_selector();
+ ::gtk_box_pack_start( GTK_BOX( vbox ), ss->widget(), FALSE, FALSE, 0 );
+ ss->update(); // seed list of available scanners
+ ss->select(); // pick one
+ ss->show();
+ #else
+ widget = create_scan_label ( );
+ ::gtk_box_pack_start ( GTK_BOX ( vbox ), widget, FALSE, FALSE, 0 );
+ ::gtk_widget_show ( widget );
+ #endif /* THE_ORIGINAL_SOURCES_WERE_NOT_SUCH_A_MESS */
+ }
+ else
+ gtk_box_pack_start (GTK_BOX (vbox), toolbar (), false, false, 0);
+
+ widget = create_notebook ( );
+ ::gtk_box_pack_start ( GTK_BOX ( vbox ), widget, FALSE, FALSE, 0 );
+ ::gtk_widget_show ( widget );
+
+ if (!_compact_mode)
+ gtk_box_pack_end (GTK_BOX (vbox), close_button_box (), false, false, 0);
+
+ return vbox;
+}
+
+/*------------------------------------------------------------*/
+GtkWidget * main_window::create_scan_label ( void )
+{
+ GtkWidget *hbox;
+ GtkWidget *widget;
+
+ hbox = ::gtk_hbox_new( FALSE, 0 );
+
+ widget = ::gtk_label_new( _("Scanner:") );
+ ::gtk_box_pack_start( GTK_BOX( hbox ), widget, FALSE, FALSE, 5 );
+ ::gtk_widget_show( widget );
+
+ widget = ::gtk_label_new( ::g_view_manager->get_device_name() );
+ ::gtk_label_set_line_wrap ( GTK_LABEL (widget), TRUE );
+ ::gtk_box_pack_start( GTK_BOX( hbox ), widget, FALSE, FALSE, 5 );
+ ::gtk_widget_show( widget );
+
+ return hbox;
+}
+
+/*------------------------------------------------------------*/
+GtkWidget * main_window::create_notebook ( void )
+{
+ GtkWidget * notebook;
+ GtkWidget * label;
+ GtkWidget * widget;
+
+ notebook = ::gtk_notebook_new ( );
+ ::gtk_container_border_width ( GTK_CONTAINER ( notebook ), 5 );
+
+ // document tab
+ label = ::gtk_label_new ( _( "Document" ) );
+ widget = create_document_tab ( );
+
+ ::gtk_notebook_append_page ( GTK_NOTEBOOK ( notebook ), widget, label );
+ ::gtk_widget_show ( label );
+ ::gtk_widget_show ( widget );
+
+ {
+ GtkWidget *center, *vbox;
+ const guint top = 15;
+
+ label = gtk_label_new (_("Image Controls"));
+ vbox = g_view_manager->get_image_controls ()->create_controls ();
+
+ add_maintenance_ui (vbox);
+
+ gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label);
+ gtk_widget_show (label);
+ gtk_widget_show_all (vbox);
+
+ label = gtk_label_new (_("Tone Correction"));
+ widget = (::g_view_manager
+ ->get_gamma_correction ()->create_controls (get_widget ()));
+
+ center = gtk_alignment_new (0.5, 0, 0, 0);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (center), top, 0, 0, 0);
+ gtk_container_add (GTK_CONTAINER (center), widget);
+
+ gtk_notebook_append_page (GTK_NOTEBOOK (notebook), center, label);
+ gtk_widget_show (label);
+ gtk_widget_show_all (center);
+ }
+ return notebook;
+}
+
+/*------------------------------------------------------------*/
+GtkWidget * main_window::create_document_tab ( void )
+{
+ GtkWidget * vbox;
+ GtkWidget * table;
+ GtkWidget * label;
+ GtkWidget * menu;
+ GtkWidget * size;
+ GtkWidget * focus;
+ GtkWidget * widget;
+ scan_manager * scan_mgr;
+
+ scan_mgr = g_view_manager->get_scan_manager ();
+
+ vbox = ::gtk_vbox_new ( FALSE, 0 );
+
+ table = ::gtk_table_new ( 3, 2, FALSE );
+ ::gtk_container_border_width ( GTK_CONTAINER ( table ), 5 );
+ ::gtk_table_set_row_spacings ( GTK_TABLE ( table ), 3 );
+ ::gtk_table_set_col_spacings ( GTK_TABLE ( table ), 5 );
+ ::gtk_box_pack_start ( GTK_BOX ( vbox ), table, FALSE, FALSE, 5 );
+ ::gtk_widget_show ( table );
+
+ label = ::gtk_label_new ( _( "Document Source:" ) );
+ ::gtk_misc_set_alignment ( GTK_MISC ( label ), 1.0, 0.5 );
+ ::gtk_table_attach ( GTK_TABLE ( table ), label, 0, 1, 0, 1,
+ GTK_FILL, GTK_FILL, 0, 0 );
+ ::gtk_widget_show ( label );
+
+ menu = ::pisa_create_option_menu ( g_docsrc_menu_item );
+ ::gtk_table_attach ( GTK_TABLE ( table ), menu, 1, 2, 0, 1,
+ GTK_FILL, GTK_FILL, 0, 0 );
+ ::gtk_widget_show ( menu );
+
+ if (!scan_mgr->has_flatbed ())
+ {
+ gtk_widget_set_sensitive (g_docsrc_menu_item[0].widget, FALSE);
+ }
+ if (!scan_mgr->has_tpu ())
+ {
+ gtk_widget_set_sensitive (g_docsrc_menu_item[1].widget, FALSE);
+ gtk_widget_set_sensitive (g_docsrc_menu_item[2].widget, FALSE);
+ }
+ if (!scan_mgr->has_adf ())
+ {
+ gtk_widget_set_sensitive (g_docsrc_menu_item[3].widget, FALSE);
+ }
+ if (!scan_mgr->has_duplex ())
+ {
+ gtk_widget_set_sensitive (g_docsrc_menu_item[4].widget, FALSE);
+ }
+ ::gtk_option_menu_set_history (GTK_OPTION_MENU (menu),
+ (scan_mgr->has_flatbed ()
+ ? 0 : (scan_mgr->has_adf ()
+ ? 3 : 1)));
+
+ label = ::gtk_label_new ( _( "Image Type:" ) );
+ ::gtk_misc_set_alignment ( GTK_MISC ( label ), 1.0, 0.5 );
+ ::gtk_table_attach ( GTK_TABLE ( table ), label, 0, 1, 1, 2,
+ GTK_FILL, GTK_FILL, 0, 0 );
+ ::gtk_widget_show ( label );
+
+ menu = ::pisa_create_option_menu ( g_imgtype_menu_item );
+ ::gtk_table_attach ( GTK_TABLE ( table ), menu, 1, 2, 1, 2,
+ GTK_FILL, GTK_FILL, 0, 0 );
+ ::gtk_widget_show ( menu );
+ m_widget [ WG_MAIN_IMG_MENU ] = menu;
+
+ label = ::gtk_label_new ( _( "Resolution:" ) );
+ ::gtk_misc_set_alignment ( GTK_MISC ( label ), 1.0, 0.5 );
+ ::gtk_table_attach ( GTK_TABLE ( table ), label, 0, 1, 2, 3,
+ GTK_FILL, GTK_FILL, 0, 0 );
+ ::gtk_widget_show ( label );
+
+ menu = ::pisa_create_option_menu ( g_resolution_menu_item );
+ ::gtk_table_attach ( GTK_TABLE ( table ), menu, 1, 2, 2, 3,
+ GTK_FILL, GTK_FILL, 0, 0 );
+ ::gtk_widget_show ( menu );
+ m_widget [ WG_MAIN_RES_MENU ] = menu;
+
+ size = create_target (scan_mgr);
+ ::gtk_box_pack_start ( GTK_BOX ( vbox ), size, FALSE, FALSE, 0 );
+ ::gtk_widget_show ( size );
+
+ if (scan_mgr->has_focus ())
+ {
+ focus = create_focus ( );
+ ::gtk_box_pack_start ( GTK_BOX ( vbox ), focus, FALSE, FALSE, 0 );
+ ::gtk_widget_show ( focus );
+ }
+
+ widget = create_options ();
+ gtk_box_pack_start (GTK_BOX (vbox), widget, TRUE, TRUE, 0 );
+ gtk_widget_show (widget);
+
+ if (!_compact_mode)
+ {
+ widget = xpm2widget (get_widget (), penguin_xpm);
+ gtk_box_pack_start (GTK_BOX (vbox), widget, TRUE, TRUE, 10 );
+ gtk_widget_show (widget);
+ }
+
+ return vbox;
+}
+
+/*------------------------------------------------------------*/
+GtkWidget * main_window::create_target (const scan_manager *sm)
+{
+ GtkWidget * frame;
+ GtkWidget * vbox;
+ GtkWidget * hbox;
+ GtkWidget * label;
+ GtkWidget * entry;
+ GtkWidget * menu;
+ GtkWidget * scale;
+ GtkWidget * separator;
+
+ frame = ::gtk_frame_new ( _( "Target" ) );
+ ::gtk_container_border_width ( GTK_CONTAINER ( frame ), 5 );
+
+ vbox = ::gtk_vbox_new ( FALSE, 5 );
+ ::gtk_container_add ( GTK_CONTAINER ( frame ), vbox );
+ ::gtk_widget_show ( vbox );
+
+ hbox = ::gtk_hbox_new ( FALSE, 5 );
+ ::gtk_box_pack_start ( GTK_BOX ( vbox ), hbox, FALSE, FALSE, 2 );
+ ::gtk_widget_show ( hbox );
+
+ label = ::gtk_label_new ( _( "W:" ) );
+ ::gtk_box_pack_start ( GTK_BOX ( hbox ), label, FALSE, FALSE, 5 );
+ ::gtk_widget_show ( label );
+
+ entry = ::gtk_entry_new ( );
+ ::gtk_widget_set_usize ( entry, 60, -1 );
+ ::gtk_entry_set_text ( GTK_ENTRY ( entry ), "8.5" );
+ ::gtk_widget_set_sensitive ( entry, FALSE );
+ ::gtk_box_pack_start ( GTK_BOX ( hbox ), entry, FALSE, FALSE, 2 );
+ ::gtk_widget_show ( entry );
+ m_widget [ WG_MAIN_WIDTH ] = entry;
+
+ label = ::gtk_label_new ( _( "H:" ) );
+ ::gtk_box_pack_start ( GTK_BOX ( hbox ), label, FALSE, FALSE, 5 );
+ ::gtk_widget_show ( label );
+
+ entry = ::gtk_entry_new ( );
+ ::gtk_widget_set_usize ( entry, 60, -1 );
+ ::gtk_entry_set_text ( GTK_ENTRY ( entry ), "11.7" );
+ ::gtk_widget_set_sensitive ( entry, FALSE );
+ ::gtk_box_pack_start ( GTK_BOX ( hbox ), entry, FALSE, FALSE, 2 );
+ ::gtk_widget_show ( entry );
+ m_widget [ WG_MAIN_HEIGHT ] = entry;
+
+ menu = ::pisa_create_option_menu ( ::g_unit_menu_item );
+ ::gtk_box_pack_start ( GTK_BOX ( hbox ), menu, FALSE, FALSE, 5 );
+ ::gtk_widget_show ( menu );
+
+ separator = ::gtk_hseparator_new ( );
+ ::gtk_box_pack_start ( GTK_BOX ( vbox ), separator, FALSE, FALSE, 2 );
+ ::gtk_widget_show ( separator );
+
+ // scale
+ hbox = ::gtk_hbox_new ( FALSE, 0 );
+ ::gtk_box_pack_start ( GTK_BOX ( vbox ), hbox, FALSE, FALSE, 2 );
+ ::gtk_widget_show ( hbox );
+
+ label = ::gtk_label_new ( _( "Scale" ) );
+ ::gtk_misc_set_alignment ( GTK_MISC ( label ), 0.0, 0.5 );
+ ::gtk_box_pack_start ( GTK_BOX ( hbox ), label, FALSE, FALSE, 5 );
+ ::gtk_widget_show ( label );
+
+ scale = ::pisa_create_scale ( & ::g_scale_zoom );
+ ::gtk_widget_set_usize ( scale, 200, -1 );
+ ::gtk_scale_set_digits ( GTK_SCALE ( scale ), 0 );
+ ::gtk_box_pack_start ( GTK_BOX ( hbox ), scale, TRUE, TRUE, 5 );
+ ::gtk_widget_show ( scale );
+
+ gtk_widget_show_all (frame);
+
+ return frame;
+}
+
+/*------------------------------------------------------------*/
+GtkWidget * main_window::create_focus ( void )
+{
+ GtkWidget * frame;
+ GtkWidget * hbox;
+ GtkWidget * radio;
+ GSList * group;
+
+ frame = ::gtk_frame_new ( _( "Focus" ) );
+ ::gtk_container_border_width ( GTK_CONTAINER ( frame ), 5 );
+
+ hbox = ::gtk_hbox_new ( FALSE, 5 );
+ ::gtk_container_border_width ( GTK_CONTAINER ( hbox ), 5 );
+ ::gtk_container_add ( GTK_CONTAINER ( frame ), hbox );
+ ::gtk_widget_show ( hbox );
+
+ radio = ::gtk_radio_button_new_with_label ( 0, "0.0" );
+ ::gtk_signal_connect ( GTK_OBJECT ( radio ), "toggled",
+ GTK_SIGNAL_FUNC ( check_focus ), & ::g_focus [ 0 ] );
+ ::gtk_box_pack_start ( GTK_BOX ( hbox ), radio, TRUE, TRUE, 0 );
+ ::gtk_widget_show ( radio );
+ m_widget [ WG_MAIN_FOCUS_0 ] = radio;
+
+ group = ::gtk_radio_button_group ( GTK_RADIO_BUTTON ( radio ) );
+ radio = ::gtk_radio_button_new_with_label ( group, "2.5" );
+ ::gtk_signal_connect ( GTK_OBJECT ( radio ), "toggled",
+ GTK_SIGNAL_FUNC ( check_focus ), & ::g_focus [ 1 ] );
+ ::gtk_box_pack_start ( GTK_BOX ( hbox ), radio, TRUE, TRUE, 0 );
+ ::gtk_widget_show ( radio );
+ m_widget [ WG_MAIN_FOCUS_25 ] = radio;
+
+ return frame;
+}
+
+/*------------------------------------------------------------*/
+GtkWidget *
+main_window::create_options (void)
+{
+ GtkContainer *frame = GTK_CONTAINER (gtk_frame_new (_("Options")));
+ GtkBox *vbox = GTK_BOX (gtk_vbox_new (false, 5));
+ scan_manager *sm = g_view_manager->get_scan_manager ();
+
+ gtk_container_border_width (frame, 5);
+ gtk_container_add (frame, GTK_WIDGET (vbox));
+ if (_compact_mode)
+ {
+ gtk_box_pack_start (vbox, scan_navi_ , false, false, 0);
+ gtk_box_pack_start (vbox, draft_mode_, false, false, 0);
+ }
+ gtk_box_pack_start (vbox, unsharp_mask_, false, false, 0);
+ gtk_box_pack_start (vbox, size_check_, false, false, 0);
+ gtk_box_pack_start (vbox, deskew_, false, false, 0);
+
+ if (getenv ("ISCAN_GUI_SHOW_ALL") || sm->has_dfd ())
+ { // double feed detection
+ gtk_box_pack_start (vbox, m_widget[WG_MAIN_DFD], false, false, 5);
+ }
+
+ gtk_widget_show_all (GTK_WIDGET (frame));
+ if (!sm->has_start_button ())
+ gtk_widget_hide (scan_navi_);
+ if (!sm->has_draft_mode ())
+ gtk_widget_hide (draft_mode_);
+
+ return GTK_WIDGET (frame);
+}
+
+//! Assembles a widget with the Configuration and Close buttons.
+/*! \note Intended for use in non-compact GUI mode only.
+ */
+GtkWidget *
+main_window::close_button_box ()
+{
+ if (_compact_mode) return NULL;
+
+ GtkWidget *vbox = gtk_vbox_new (true, 5);
+ GtkWidget *hbox = gtk_hbutton_box_new ();
+
+ gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_SPREAD);
+
+ gtk_box_pack_start (GTK_BOX (vbox), gtk_hseparator_new (), false, false, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, false, false, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), config_, false, false, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), close_button (), false, false, 0);
+
+ gtk_widget_show_all (vbox);
+
+ return vbox;
+}
+
+/*------------------------------------------------------------*/
+void main_window::sensitive_image_type ( void )
+{
+ gboolean enable_bw;
+ int i;
+
+ if (g_view_manager->get_scan_manager ()->using_tpu ())
+ enable_bw = FALSE;
+ else
+ enable_bw = TRUE;
+
+ for ( i = 0; ::g_imgtype_menu_item [ i ].id != 0; i++ )
+ {
+ if ( ::g_imagetype_list [ i ].pixeltype == PISA_PT_BW )
+ ::gtk_widget_set_sensitive ( ::g_imgtype_menu_item [ i ].widget, enable_bw );
+ }
+
+ for ( i = 0; ::g_imgtype_menu_item [ i ].id != 0; i++ )
+ {
+ if ( ::g_imgtype_menu_item [ i ].id == g_view_manager->get_settings ().imgtype.id )
+ {
+ ::gtk_option_menu_set_history ( GTK_OPTION_MENU ( m_widget [ WG_MAIN_IMG_MENU ] ), i );
+ break;
+ }
+ }
+}
+
+/*------------------------------------------------------------*/
+void main_window::sensitive_target ( void )
+{
+ long resolution, unit, scale, scale_px;
+ double w, h;
+ char w_buf [ 32 ], h_buf [ 32 ];
+ marquee * marq;
+ scan_manager * scan_mgr;
+
+ scan_mgr = g_view_manager->get_scan_manager ();
+
+ resolution = g_view_manager->get_settings ().resolution;
+ unit = g_view_manager->get_settings ().unit;
+
+ marq = & g_view_manager->get_marquee ();
+
+ w = marq->area.x;
+ h = marq->area.y;
+ scale = marq->scale;
+
+ resolution = (resolution * scale + 50) / 100;
+
+ scan_mgr->adjust_scan_param (&resolution, &scale_px);
+
+ switch ( unit )
+ {
+ case PISA_UNIT_INCHES:
+ w = ::inches_reflect_zoom ( w, scale ) + 0.005;
+ h = ::inches_reflect_zoom ( h, scale ) + 0.005;
+ ::sprintf ( w_buf, "%u.%02u",
+ ( int ) w,
+ ( int ) ( w * 100 - ( int ) w * 100 ) );
+ ::sprintf ( h_buf, "%u.%02u",
+ ( int ) h,
+ ( int ) ( h * 100 - ( int ) h * 100 ) );
+ break;
+
+ case PISA_UNIT_PIXELS:
+ w = ::inch2width ( w, resolution, scale_px,
+ PISA_PT_BW == g_view_manager->get_settings ().imgtype.pixeltype);
+ h = ::inch2height ( h, resolution, scale_px );
+ ::sprintf ( w_buf, "%u", ( int ) w );
+ ::sprintf ( h_buf, "%u", ( int ) h );
+ break;
+
+ case PISA_UNIT_CM:
+ w = ::inch2centi ( w, scale ) + 0.005;
+ h = ::inch2centi ( h, scale ) + 0.005;
+ ::sprintf ( w_buf, "%u.%02u",
+ ( int ) w,
+ ( int ) ( w * 100 - ( int ) w * 100 ) );
+ ::sprintf ( h_buf, "%u.%02u",
+ ( int ) h,
+ ( int ) ( h * 100 - ( int ) h * 100 ) );
+ break;
+ }
+
+ ::gtk_entry_set_text ( GTK_ENTRY ( m_widget [ WG_MAIN_WIDTH ] ), w_buf );
+ ::gtk_entry_set_text ( GTK_ENTRY ( m_widget [ WG_MAIN_HEIGHT ] ), h_buf );
+}
+
+/*------------------------------------------------------------*/
+void main_window::sensitive_resolution ( void )
+{
+ long max_resolution, i, resolution;
+
+ max_resolution = ::g_view_manager->get_scan_manager()->get_max_resolution ();
+
+ for ( i = 0; ::g_resolution_menu_item [ i ].id != 0; i++ )
+ {
+ if ( max_resolution < ::g_resolution_menu_item [ i ].id )
+ ::gtk_widget_set_sensitive ( ::g_resolution_menu_item [ i ].widget,
+ FALSE );
+ else
+ ::gtk_widget_set_sensitive ( ::g_resolution_menu_item [ i ].widget,
+ TRUE );
+ }
+
+ resolution = g_view_manager->get_settings ().resolution;
+
+ for ( i = 0; ::g_resolution_menu_item [ i ].id != 0; i++ )
+ {
+ if ( ::g_resolution_menu_item [ i ].id == resolution )
+ {
+ ::gtk_option_menu_set_history ( GTK_OPTION_MENU ( m_widget [ WG_MAIN_RES_MENU ] ), i );
+ break;
+ }
+ }
+}
+
+/*------------------------------------------------------------*/
+void main_window::sensitive_scale ( void )
+{
+ const long min_scale = 50;
+ const long max_scale = 200;
+ GtkAdjustment * adjust;
+ marquee * marq;
+
+ marq = & g_view_manager->get_marquee ();
+
+ if ( marq->scale < min_scale )
+ marq->scale = min_scale;
+ if ( marq->scale > max_scale )
+ marq->scale = max_scale;
+
+ adjust = ::gtk_range_get_adjustment ( GTK_RANGE ( ::g_scale_zoom.widget ) );
+ adjust->lower = min_scale;
+ adjust->upper = max_scale;
+ adjust->value = marq->scale;
+
+ ::gtk_signal_emit_by_name ( GTK_OBJECT ( ::g_scale_zoom.adjust ),
+ "changed" );
+}
+
+/*------------------------------------------------------------*/
+void main_window::sensitive_focus ( void )
+{
+ scan_manager * scan_mgr;
+ marquee * marq;
+
+ scan_mgr = g_view_manager->get_scan_manager ();
+
+ if (!scan_mgr->has_focus ())
+ return;
+
+ marq = & g_view_manager->get_marquee ();
+
+ if (m_widget[WG_MAIN_FOCUS_25] && marq->focus == 25)
+ ::gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON ( m_widget [ WG_MAIN_FOCUS_25 ] ),
+ TRUE );
+ if (m_widget[WG_MAIN_FOCUS_0 ] && marq->focus == 0)
+ ::gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON ( m_widget [ WG_MAIN_FOCUS_0 ] ),
+ TRUE );
+
+}
+
+/*------------------------------------------------------------*/
+void main_window::sensitive_usm ( void )
+{
+ settings s = g_view_manager->get_settings ();
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (unsharp_mask_), s.usm);
+ gtk_widget_set_sensitive (unsharp_mask_, PISA_PT_BW != s.imgtype.pixeltype);
+}
+
+void
+main_window::do_sensitive_size_check (bool yes)
+{
+ _do_sensitive_size_check = yes;
+}
+
+void
+main_window::sensitive_size_check (void)
+{
+ scan_manager *sm = g_view_manager->get_scan_manager ();
+ bool size_check_active = false;
+ bool autocrop_active = false;
+
+ if (!_do_sensitive_size_check) return;
+
+ if (sm->has_size_check ())
+ {
+ size_check_active = sm->get_size_check ();
+ }
+ if (sm->has_autocrop ())
+ {
+ autocrop_active = sm->get_autocrop ();
+ }
+ if (sm->has_size_check () || sm->has_autocrop ())
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (size_check_),
+ (size_check_active || autocrop_active) );
+ }
+ gtk_widget_set_sensitive (size_check_,
+ (sm->has_size_check () || sm->has_autocrop () ));
+}
+
+void
+main_window::sensitive_deskew (void)
+{
+ scan_manager *sm = g_view_manager->get_scan_manager ();
+
+ if (!_do_sensitive_size_check) return;
+
+ if (sm->has_deskew ())
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (deskew_),
+ sm->get_deskew () );
+ }
+ gtk_widget_set_sensitive (deskew_, sm->has_deskew ());
+}
+
+void
+main_window::add_maintenance_ui (GtkWidget *vbox)
+{
+ GtkWidget *separator;
+ GtkWidget *bbox;
+ GtkWidget *button;
+ scan_manager *scan_mgr = g_view_manager->get_scan_manager ();
+
+ if (scan_mgr->has_clean () || scan_mgr->has_calibrate ())
+ {
+ // add Calibration & Cleaning button
+ separator = ::gtk_hseparator_new ( );
+ ::gtk_box_pack_start ( GTK_BOX ( vbox ), separator, FALSE, FALSE, 2 );
+ ::gtk_widget_show ( separator );
+
+ bbox = ::gtk_vbutton_box_new ();
+ ::gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD);
+
+ if (scan_mgr->has_calibrate ())
+ {
+ button = ::gtk_button_new_with_label ( _( "Calibration" ) );
+ ::gtk_container_border_width ( GTK_CONTAINER ( button ), 5 );
+ ::gtk_box_pack_start (GTK_BOX (bbox), button, false, false, 0);
+ ::g_signal_connect (GTK_OBJECT (button), "clicked",
+ GTK_SIGNAL_FUNC (click_calibration), scan_mgr);
+
+ ::gtk_widget_show ( button );
+ }
+
+ if (scan_mgr->has_clean ())
+ {
+ button = ::gtk_button_new_with_label ( _( "Cleaning" ) );
+ ::gtk_container_border_width ( GTK_CONTAINER ( button ), 5 );
+ ::gtk_box_pack_start (GTK_BOX (bbox), button, false, false, 0);
+ ::g_signal_connect (GTK_OBJECT (button), "clicked",
+ GTK_SIGNAL_FUNC (click_cleaning), scan_mgr);
+
+ ::gtk_widget_show ( button );
+ }
+
+ ::gtk_box_pack_start (GTK_BOX (vbox), bbox, true, false, 0);
+ ::gtk_widget_show ( bbox );
+
+ // for padding
+ ::gtk_box_pack_start (GTK_BOX (vbox), gtk_vbox_new (false, 0), true, true, 0);
+ }
+}
diff --git a/frontend/pisa_main_window.h b/frontend/pisa_main_window.h
new file mode 100644
index 0000000..8067de9
--- /dev/null
+++ b/frontend/pisa_main_window.h
@@ -0,0 +1,112 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001, 2008, 2009 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_MAIN_WINDOW_H
+#define ___PISA_MAIN_WINDOW_H
+
+#include <gtk/gtk.h>
+#include "pisa_enums.h"
+#include "pisa_scan_manager.h"
+
+class main_window
+{
+ public:
+
+ int init ( void );
+ GtkWidget * create_window ( GtkWidget * parent = 0 );
+ int close_window ( int destroy );
+ void start_scan (const GtkWidget *destination) const;
+ void start_preview ( bool zooming );
+ void set_double_feed_detection (const GtkWidget *widget);
+ void sensitive ( int is_prev_img );
+ void sensitive_option ( );
+ void enable_start_button (bool yes);
+ void update_zoom_button ();
+ void do_sensitive_size_check (bool yes);
+
+ GtkWidget * get_widget (int wg_id = WG_MAIN_TOP) { return m_widget[wg_id]; }
+
+private:
+
+ void create_controls ();
+ GtkWidget *tool_button (const char *text, GCallback callback, char **icon);
+ GtkWidget *check_button (const char *text,
+ void (*callback) (GtkWidget*, gpointer));
+ GtkWidget *close_button ();
+
+ GtkWidget *action_button_box ();
+ GtkWidget *toolbar ();
+ GtkWidget *close_button_box ();
+
+ GtkWidget * create_left_area ( void );
+ GtkWidget * create_scan_button ( void );
+ GtkWidget * create_scan_label ( void );
+ GtkWidget * create_right_area ( void );
+ GtkWidget * create_notebook ( void );
+ GtkWidget * create_document_tab ( void );
+ GtkWidget * create_target (const scan_manager *sm);
+ GtkWidget * create_focus ( void );
+ GtkWidget * create_options ( void );
+
+ void add_maintenance_ui (GtkWidget *vbox);
+
+ void sensitive_image_type ( void );
+ void sensitive_target ( void );
+ void sensitive_resolution ( void );
+ void sensitive_scale ( void );
+ void sensitive_focus ( void );
+ void sensitive_usm ( void );
+ void sensitive_size_check (void);
+ void sensitive_deskew (void);
+
+ GtkWidget *prev_btn_;
+ GtkWidget *zoom_btn_;
+ GtkWidget *expo_btn_;
+
+ GtkWidget *scan_btn_;
+ GtkWidget *print_btn_;
+ GtkWidget *dest_menu_;
+
+ GtkWidget *scan_navi_;
+ GtkWidget *draft_mode_;
+ GtkWidget *size_check_;
+ GtkWidget *deskew_;
+
+ GtkWidget *config_;
+
+ GtkWidget *unsharp_mask_;
+
+ GtkWidget * m_widget [ WG_MAIN_NUM ];
+ bool _done_preview;
+ bool _do_sensitive_size_check;
+ bool _compact_mode;
+};
+
+#endif // ___PISA_MAIN_WINDOW_H
diff --git a/frontend/pisa_marquee.cc b/frontend/pisa_marquee.cc
new file mode 100644
index 0000000..d3f6645
--- /dev/null
+++ b/frontend/pisa_marquee.cc
@@ -0,0 +1,117 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+/*--------------------------------------------------------------*/
+#include "pisa_marquee.h"
+#include "pisa_default_val.h"
+
+/*--------------------------------------------------------------*/
+marquee::marquee (const double w, const double h,
+ const long f, const long b, const long c) :
+ active (0),
+ offset (0.0, 0.0),
+ area (w, h),
+ scale (100),
+ gamma (( long ) ( 100 * DEFGAMMA )),
+ highlight (DEFHIGHLIGHT),
+ shadow (DEFSHADOW),
+ threshold (DEFTHRESHOLD),
+ brightness (b),
+ contrast (c),
+ graybalance (DEFGRAYBALANCE),
+ saturation (DEFSATURATION),
+ focus (f)
+{
+ int i, j;
+
+ for ( i = 0; i < 4; i++ )
+ for ( j = 0; j < 256; j++ )
+ gamma_table [ i ] [ j ] = j;
+
+ for ( i = 0; i < 3; i++ )
+ {
+ film_gamma [ i ] = 1.0;
+ film_yp [ i ] = 0.0;
+ grayl [ i ] = 0.0;
+ }
+
+ for ( i = 0; i < 256; i++ )
+ {
+ lut.gamma_r [ i ] = i;
+ lut.gamma_g [ i ] = i;
+ lut.gamma_b [ i ] = i;
+ }
+}
+
+marquee & marquee::operator= ( const marquee & x )
+{
+ if ( & x != this )
+ {
+ int i, j;
+
+ active = x.active;
+ offset = x.offset;
+ area = x.area;
+ scale = x.scale;
+ gamma = x.gamma;
+ highlight = x.highlight;
+ shadow = x.shadow;
+ threshold = x.threshold;
+ brightness = x.brightness;
+ contrast = x.contrast;
+
+ for ( i = 0; i < 4; i++ )
+ for ( j = 0; j < 256; j++ )
+ gamma_table [ i ] [ j ] = x.gamma_table [ i ] [ j ];
+
+ graybalance = x.graybalance;
+ saturation = x.saturation;
+
+ focus = x.focus;
+
+ for ( i = 0; i < 3; i++ )
+ {
+ film_gamma [ i ] = x.film_gamma [ i ];
+ film_yp [ i ] = x.film_yp [ i ];
+ grayl [ i ] = x.grayl [ i ];
+ }
+
+ for ( i = 0; i < 256; i++ )
+ {
+ lut.gamma_r [ i ] = x.lut.gamma_r [ i ];
+ lut.gamma_g [ i ] = x.lut.gamma_g [ i ];
+ lut.gamma_b [ i ] = x.lut.gamma_b [ i ];
+ }
+ }
+
+ return ( * this );
+}
+
+/*--------------------------------------------------------------*/
diff --git a/frontend/pisa_marquee.h b/frontend/pisa_marquee.h
new file mode 100644
index 0000000..096381b
--- /dev/null
+++ b/frontend/pisa_marquee.h
@@ -0,0 +1,83 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_MARQUEE_H
+#define ___PISA_MARQUEE_H
+
+#include "pisa_structs.h"
+
+/*--------------------------------------------------------------*/
+class marquee
+{
+ public:
+
+ long active;
+
+ _pointD offset;
+ _pointD area;
+
+ long scale;
+
+ long gamma;
+ long highlight;
+ long shadow;
+ long threshold;
+ long brightness;
+ long contrast;
+
+ unsigned char gamma_table [ 4 ] [ 256 ]; // mono,red,green,blue
+
+ long graybalance;
+ long saturation;
+
+ long focus;
+
+ double film_gamma [ 3 ];
+ double film_yp [ 3 ];
+ double grayl [ 3 ];
+
+ gamma_struct lut;
+
+ marquee (const double w = 8.5, const double h = 11.7,
+ const long f = 0, const long b = 0, const long c = 0);
+
+ marquee & operator= ( const marquee & x );
+};
+
+
+/*--------------------------------------------------------------*/
+typedef struct _marquee_node
+{
+ _marquee_node * next;
+ marquee * data;
+} marquee_node;
+
+#endif // ___PISA_MARQUEE_H
+
diff --git a/frontend/pisa_preference.cc b/frontend/pisa_preference.cc
new file mode 100644
index 0000000..89b9c15
--- /dev/null
+++ b/frontend/pisa_preference.cc
@@ -0,0 +1,351 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/13/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include "pisa_preference.h"
+
+static char * get_line ( FILE * fp, char ** gotstr );
+static char * dynamic_fgets ( FILE * fp );
+static int parse_line ( char * ptr, cfg_struct * cfg );
+static char * parse_word ( char * ptr, char ** word, cfg_key_type key );
+static int store_value ( cfg_struct * cfg, char * parameter, char * value );
+static char * remove_first_spaces ( char * ptr );
+static void set_word ( FILE * fp, cfg_struct * cfg );
+static void single_or_double_quote ( char * str, char * ret );
+
+/*----------------------------------------------------------*/
+void get_cfg ( char * file, cfg_struct * cfg, int num )
+{
+ FILE * fp;
+ char * ptr;
+ char * line_buf;
+ int i;
+
+ if ( 0 == ( fp = fopen ( file, "r" ) ) )
+ return;
+
+ for ( i = 0; i < num; i++, cfg++ )
+ {
+ rewind ( fp );
+
+ while ( 0 != ( ptr = get_line ( fp, & line_buf ) ) )
+ {
+ if ( parse_line ( ptr, cfg ) )
+ {
+ free ( line_buf );
+ break;
+ }
+
+ free ( line_buf );
+ }
+ }
+
+ fclose ( fp );
+}
+
+/*----------------------------------------------------------*/
+void set_cfg ( char * file, cfg_struct * cfg, int num )
+{
+ FILE * fp;
+ int i;
+
+ if ( 0 == ( fp = fopen ( file, "w" ) ) )
+ return;
+
+ for ( i = 0; i < num; i++, cfg++ )
+ set_word ( fp, cfg );
+
+ fclose ( fp );
+}
+
+/*----------------------------------------------------------*/
+static char * get_line ( FILE * fp, char ** gotstr )
+{
+ char * ptr;
+
+ while ( 1 )
+ {
+ if ( 0 == ( * gotstr = dynamic_fgets ( fp ) ) )
+ return 0;
+
+ ptr = remove_first_spaces ( * gotstr );
+ if ( * ptr != '#' && * ptr != '\0' )
+ return ptr;
+
+ free ( * gotstr );
+ }
+}
+
+/*----------------------------------------------------------*/
+static char * dynamic_fgets ( FILE * fp )
+{
+ char * ptr;
+ char tmp [ 128 ];
+ int i;
+
+ if ( 0 == ( ptr = ( char * ) malloc ( 1 ) ) )
+ return 0;
+
+ * ptr = '\0';
+
+ for ( i = 0;; i++ )
+ {
+ if ( 0 == fgets ( tmp, 128, fp ) )
+ {
+ free ( ptr );
+ return 0;
+ }
+
+ if ( 0 == ( ptr = ( char * ) realloc ( ptr, 127 * ( i + 1 ) + 1 ) ) )
+ return 0;
+
+ strcat ( ptr, tmp );
+
+ if ( 0 != strchr ( tmp, '\n' ) )
+ {
+ * strchr ( ptr, '\n' ) = '\0';
+ return ptr;
+ }
+
+ if ( 0 != feof ( fp ) )
+ return ptr;
+ }
+}
+
+/*----------------------------------------------------------*/
+static char * remove_first_spaces ( char * ptr )
+{
+ while ( * ptr == ' ' || * ptr == '\t' )
+ ptr++;
+
+ return ptr;
+}
+
+/*----------------------------------------------------------*/
+static int parse_line ( char * ptr, cfg_struct * cfg )
+{
+ char * parameter;
+ char * value;
+
+ if ( 0 == ( ptr = parse_word ( ptr, & parameter, CFG_PARAM ) ) )
+ return 0;
+
+ if ( 0 == ( ptr = parse_word ( ptr, & value, CFG_VALUE ) ) )
+ return 0;
+
+ if ( 0 == store_value ( cfg, parameter, value ) )
+ return 0;
+
+ free ( parameter );
+ free ( value );
+
+ return 1;
+}
+
+/*----------------------------------------------------------*/
+static char * parse_word ( char * ptr, char ** word, cfg_key_type key )
+{
+ int len = 0;
+ cfg_quote_type quote;
+
+ switch ( * ptr )
+ {
+ case '\"':
+ quote = CFG_QUOTE_DOUBLE;
+ ptr++;
+ break;
+ case '\'':
+ quote = CFG_QUOTE_SINGLE;
+ ptr++;
+ break;
+ default:
+ quote = CFG_QUOTE_NO;
+ break;
+ }
+
+ while ( 1 )
+ {
+ if ( quote == CFG_QUOTE_NO )
+ {
+ if ( * ( ptr + len ) == ' ' || * ( ptr + len ) == '\t' ||
+ * ( ptr + len ) == '\0' || * ( ptr + len ) == '#' ||
+ ( * ( ptr + len ) == '=' && key == CFG_PARAM ) )
+ break;
+ }
+ else if ( quote == CFG_QUOTE_DOUBLE )
+ {
+ if ( * ( ptr + len ) == '\"' )
+ break;
+ }
+ else if ( quote == CFG_QUOTE_SINGLE )
+ {
+ if ( * ( ptr + len ) == '\'' )
+ break;
+ }
+
+ if ( * ( ptr + len ) == '\0' )
+ return 0;
+
+ len++;
+ }
+
+ if ( 0 == ( * word = ( char * ) malloc ( len + 1 ) ) )
+ return 0;
+
+ strncpy ( * word, ptr, len );
+ * ( * word + len ) = '\0';
+
+ ptr += ( len + ( quote == CFG_QUOTE_NO ? 0 : 1 ) );
+
+ ptr = remove_first_spaces ( ptr );
+
+ switch ( key )
+ {
+ case CFG_PARAM:
+ if ( * ptr != '=' )
+ return 0;
+ ptr++;
+ ptr = remove_first_spaces ( ptr );
+ break;
+ case CFG_VALUE:
+ if ( * ptr != '\0' && * ptr != '#' )
+ return 0;
+ break;
+ }
+
+ return ptr;
+}
+
+/*----------------------------------------------------------*/
+static int store_value ( cfg_struct * cfg, char * parameter, char * value )
+{
+ long tmp, utmp;
+ double dtmp;
+ char * endptr;
+
+ if ( 0 != strcasecmp ( parameter, cfg->name ) )
+ return 0;
+
+ errno = 0;
+
+ switch ( cfg->type )
+ {
+ case CFG_BOOL:
+ if ( 0 == strcasecmp ( value, "TRUE" ) ||
+ 0 == strcasecmp ( value, "YES" ) ||
+ 0 == strcasecmp ( value, "T" ) ||
+ 0 == strcasecmp ( value, "Y" ) ||
+ 0 == strcasecmp ( value, "1" ) )
+ * ( int * ) ( cfg->value ) = 1;
+ else
+ * ( int * ) ( cfg->value ) = 0;
+ return 1;
+ case CFG_LONG:
+ tmp = strtol ( value, & endptr, 10 );
+ if ( * endptr )
+ return 0;
+ if ( errno == ERANGE )
+ return 0;
+ * ( long * ) ( cfg->value ) = tmp;
+ return 1;
+ case CFG_ULONG:
+ utmp = strtoul ( value, & endptr, 10 );
+ if ( * endptr )
+ return 0;
+ if ( errno == ERANGE )
+ return 0;
+ * ( unsigned long * ) ( cfg->value ) = utmp;
+ return 1;
+ case CFG_DOUBLE:
+ dtmp = strtod ( value, & endptr );
+ if ( * endptr )
+ return 0;
+ if ( errno == ERANGE )
+ return 0;
+ * ( double * ) ( cfg->value ) = dtmp;
+ return 1;
+ case CFG_STRING:
+ strcpy ( ( char * ) cfg->value, value );
+ return 1;
+ default:
+ return 0;
+ }
+
+ return 0;
+}
+
+/*----------------------------------------------------------*/
+static void set_word ( FILE * fp, cfg_struct * cfg )
+{
+ char c [ 2 ];
+
+ switch ( cfg->type )
+ {
+ case CFG_BOOL:
+ fprintf ( fp, "%s\t= %s\n", cfg->name,
+ ( * ( int * ) ( cfg->value ) ) ? "true" : "false" );
+ break;
+ case CFG_LONG:
+ fprintf ( fp, "%s\t= %ld\n", cfg->name, * ( long * ) ( cfg->value ) );
+ break;
+ case CFG_ULONG:
+ fprintf ( fp, "%s\t= %lu\n", cfg->name,
+ * ( unsigned long * ) ( cfg->value ) );
+ break;
+ case CFG_DOUBLE:
+ fprintf ( fp, "%s\t= %f\n", cfg->name, * ( double * ) ( cfg->value ) );
+ break;
+ case CFG_STRING:
+ single_or_double_quote ( ( char * ) cfg->value, c );
+ fprintf ( fp, "%s\t= %s%s%s\n", cfg->name,
+ c, ( char * ) cfg->value, c );
+ break;
+ }
+}
+
+/*----------------------------------------------------------*/
+static void single_or_double_quote ( char * str, char * ret )
+{
+ ret [ 1 ] = '\0';
+
+ if ( 0 != strchr ( str, '\"' ) )
+ ret [ 0 ] = '\'';
+ else if ( 0 != strchr ( str, '\'' ) ||
+ 0 != strchr ( str, '#' ) ||
+ 0 != strchr ( str, '\t' ) ||
+ 0 != strchr ( str, ' ' ) )
+ ret [ 0 ] = '\"';
+ else
+ ret [ 0 ] = '\0';
+}
+
diff --git a/frontend/pisa_preference.h b/frontend/pisa_preference.h
new file mode 100644
index 0000000..190d877
--- /dev/null
+++ b/frontend/pisa_preference.h
@@ -0,0 +1,68 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001, 2008 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/13/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+
+#ifndef ___PISA_PREFERENCE_H
+#define ___PISA_PREFERENCE_H
+
+typedef enum
+{
+ CFG_BOOL,
+ CFG_LONG,
+ CFG_ULONG,
+ CFG_DOUBLE,
+ CFG_STRING
+} cfg_value_type;
+
+typedef enum
+{
+ CFG_PARAM,
+ CFG_VALUE
+} cfg_key_type;
+
+typedef enum
+{
+ CFG_QUOTE_NO,
+ CFG_QUOTE_SINGLE,
+ CFG_QUOTE_DOUBLE
+} cfg_quote_type;
+
+typedef struct
+{
+ const char * name;
+ cfg_value_type type;
+ void * value;
+} cfg_struct;
+
+void get_cfg ( char * file, cfg_struct * cfg, int num );
+void set_cfg ( char * file, cfg_struct * cfg, int num );
+
+#endif // ___PISA_PREFERENCE_H
+
diff --git a/frontend/pisa_preview_window.cc b/frontend/pisa_preview_window.cc
new file mode 100644
index 0000000..1928179
--- /dev/null
+++ b/frontend/pisa_preview_window.cc
@@ -0,0 +1,1844 @@
+/* pisa_preview_window.cc
+ Copyright (C) 2001, 2004, 2005, 2008, 2009 SEIKO EPSON CORPORATION
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+/*------------------------------------------------------------*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*------------------------------------------------------------*/
+#include "pisa_view_manager.h"
+#include "pisa_preview_window.h"
+#include "pisa_error.h"
+#include "pisa_scan_tool.h"
+#include "pisa_tool.h"
+#include "pisa_default_val.h"
+#include "pisa_aleart_dialog.h"
+#include "pisa_change_unit.h"
+
+/*------------------------------------------------------------*/
+long g_prev_max_x = 320;
+long g_prev_max_y = 440;
+
+const double g_min_marq_size = 0.4;
+
+/*------------------------------------------------------------*/
+static gint expose_event ( GtkWidget * widget,
+ GdkEventExpose * event )
+{
+ preview_window * prev_cls;
+
+ prev_cls = ( preview_window * ) ::g_view_manager->get_window_cls ( ID_WINDOW_PREV );
+
+ return prev_cls->expose_event ( widget, event );
+}
+
+
+/*------------------------------------------------------------*/
+static gint event ( GtkWidget * widget,
+ GdkEvent * event )
+{
+ preview_window * prev_cls;
+
+ prev_cls = ( preview_window * ) ::g_view_manager->get_window_cls ( ID_WINDOW_PREV );
+
+ return prev_cls->event ( widget, event );
+}
+
+/*------------------------------------------------------------*/
+static void size_allocate ( GtkWidget * widget )
+{
+ preview_window * prev_cls;
+
+ prev_cls = ( preview_window * ) ::g_view_manager->get_window_cls ( ID_WINDOW_PREV );
+
+ prev_cls->size_allocate ( widget );
+}
+
+/*------------------------------------------------------------*/
+int preview_window::init ( void )
+{
+ int i;
+
+ m_gc = 0;
+
+ m_img_width = g_prev_max_x;
+ m_img_height = g_prev_max_y;
+
+ m_is_prev = 0;
+ m_drag = 0;
+
+ m_img = new unsigned char [ g_prev_max_x * g_prev_max_y * 3 ];
+
+ if ( m_img == 0 )
+ return PISA_ERR_OUTOFMEMORY;
+
+ m_img_org = new unsigned char [ g_prev_max_x * g_prev_max_y * 3 ];
+
+ if ( m_img_org == 0 )
+ {
+ delete [ ] m_img;
+ m_img = 0;
+ return PISA_ERR_OUTOFMEMORY;
+ }
+
+ for ( i = 0; i < 11; i++ )
+ m_cursor [ i ] = 0;
+
+ m_on_preview = false;
+ m_allocate_width = 0;
+ m_allocate_height = 0;
+
+ return PISA_ERR_SUCCESS;
+}
+
+/*------------------------------------------------------------*/
+GtkWidget * preview_window::create_window ( GtkWidget * parent )
+{
+ m_win = create_darea ( parent );
+
+ create_cursor ( );
+
+ return m_win;
+}
+
+/*------------------------------------------------------------*/
+int preview_window::close_window ( int destroy )
+{
+ destroy = destroy;
+
+ if ( m_gc )
+ {
+ ::gdk_gc_destroy ( m_gc );
+ m_gc = 0;
+ }
+
+ if ( m_img )
+ {
+ delete [ ] m_img;
+ m_img = 0;
+ }
+
+ if ( m_img_org )
+ {
+ delete [ ] m_img_org;
+ m_img_org = 0;
+ }
+
+ return PISA_ERR_SUCCESS;
+}
+
+/*------------------------------------------------------------*/
+int preview_window::resize_window ( void )
+{
+ get_preview_resolution ( );
+ resize_preview_window ( 0, 0 );
+
+ clear_image ( );
+
+ return 0;
+}
+
+/*------------------------------------------------------------*/
+int preview_window::auto_exposure ( void )
+{
+ pisa_image_info info;
+ _rectL region;
+ marquee tmp_marq, * marq;
+ int i, marq_num;
+
+ scan_manager *scan_mgr = g_view_manager->get_scan_manager ();
+
+ if ( m_is_prev == 0 ||
+ g_view_manager->get_settings ().imgtype.pixeltype == PISA_PT_BW )
+ {
+ return PISA_ERR_SUCCESS;
+ }
+
+ info.m_img = m_img_org;
+ info.m_width = m_img_width;
+ info.m_height = m_img_height;
+ info.m_rowbytes = g_prev_max_x * 3;
+
+ marq_num = g_view_manager->get_marquee_size ();
+ marq = & g_view_manager->get_marquee ();
+
+ if ( marq_num < 2 )
+ {
+ region.left = 0;
+ region.top = 0;
+ region.right = m_img_width - 1;
+ region.bottom = m_img_height - 1;
+ }
+ else
+ {
+ _pointL pt_lt, pt_rb;
+
+ get_marquee_point ( marq_num - 1, & pt_lt, & pt_rb );
+
+ region.left = pt_lt.x;
+ region.top = pt_lt.y;
+ region.right = pt_rb.x;
+ region.bottom = pt_rb.y;
+ }
+
+ settings set = g_view_manager->get_settings ();
+ scan_manager *sm = g_view_manager->get_scan_manager ();
+ iscan::auto_expose (sm->get_scan_source (),
+ sm->get_film_type (),
+ info, region, tmp_marq,
+ set.imgtype.ae_option != PISA_AE_DOC,
+ !scan_mgr->has_zoom ());
+
+ marq->gamma = tmp_marq.gamma;
+ marq->highlight = tmp_marq.highlight;
+ marq->shadow = tmp_marq.shadow;
+ marq->graybalance = tmp_marq.graybalance;
+
+ for ( i = 0; i < 3; i++ )
+ {
+ marq->film_gamma [ i ] = tmp_marq.film_gamma [ i ];
+ marq->film_yp [ i ] = tmp_marq.film_yp [ i ];
+ marq->grayl [ i ] = tmp_marq.grayl [ i ];
+ }
+
+ ::g_view_manager->sensitive ( );
+ ::g_view_manager->update_lut ( );
+
+ return PISA_ERR_SUCCESS;
+}
+
+/*------------------------------------------------------------*/
+int preview_window::update_img ( bool left )
+{
+ int i;
+ pisa_image_info img_info;
+ marquee * marq;
+ double delta;
+ long curr_width, adj_width, resolution;
+
+ if ( m_is_prev == 0 )
+ {
+ clear_image ( );
+ return PISA_ERR_SUCCESS;
+ }
+
+ settings set = g_view_manager->get_settings ();
+ marq = & g_view_manager->get_marquee ();
+
+ resolution = (set.resolution * (marq->scale)) / 100;
+ curr_width = ::inch2pixel ( marq->area.x, resolution );
+ adj_width = ::inch2width ( marq->area.x, resolution );
+ delta = marq->area.x - (marq->area.x) * adj_width /curr_width ;
+
+ // Resize the area
+ marq -> area.x -= delta;
+ if ( left )
+ {
+ // If the left side -> increase the x offset
+ marq ->offset.x += delta;
+ }
+
+ if ( m_img == 0 || m_img_org == 0 )
+ return PISA_ERR_OUTOFMEMORY;
+
+ ::memcpy ( m_img, m_img_org, g_prev_max_x * g_prev_max_y * 3 );
+
+ img_info.m_img = m_img;
+ img_info.m_width = m_img_width;
+ img_info.m_height = m_img_height;
+ img_info.m_rowbytes = g_prev_max_x * 3;
+
+ ::tool_lut ( & img_info, & marq->lut );
+
+ switch (set.imgtype.pixeltype)
+ {
+ case PISA_PT_RGB:
+ tool_matrix (&img_info, marq->saturation, set.coef);
+ if (set.usm)
+ tool_usm (img_info);
+ break;
+
+ case PISA_PT_GRAY:
+ build_gray (&img_info, set.imgtype.dropout);
+ if (set.usm)
+ tool_usm (img_info);
+ break;
+
+ case PISA_PT_BW:
+ build_bw (&img_info,
+ set.imgtype.dropout,
+ set.imgtype.halftone,
+ marq->threshold);
+ break;
+ }
+
+ for ( i = 0; i < m_img_height; i++ )
+ ::gtk_preview_draw_row ( GTK_PREVIEW ( m_prev ),
+ m_img + i * g_prev_max_x * 3,
+ 0, i, m_img_width );
+
+ ::gtk_widget_draw ( m_prev, 0 );
+ ::gdk_flush ( );
+
+ return PISA_ERR_SUCCESS;
+}
+
+void
+preview_window::start_preview (bool zooming)
+{
+ scan_manager * scan_mgr =
+ g_view_manager->get_scan_manager ( );
+ int width, height;
+ int cancel;
+
+ cancel = 0;
+
+ m_is_prev = 0;
+
+ m_on_preview = true;
+ m_allocate_width = 0;
+ m_allocate_height = 0;
+
+ progress_window feedback
+ (static_cast <main_window *> (g_view_manager
+ ->get_window_cls (ID_WINDOW_MAIN))
+ ->get_widget());
+ if (scan_mgr->push_button_needs_polling ())
+ {
+ feedback.set_text (progress_window::WAITING);
+ }
+ else
+ {
+ feedback.set_text (progress_window::WARMING_UP);
+ }
+ feedback.show ();
+
+ while ( ::gtk_events_pending ( ) )
+ ::gtk_main_iteration ( );
+
+ // preview
+ set_preview_param (zooming);
+
+ reset_settings (zooming);
+ ::g_view_manager->sensitive ( );
+
+ try
+ {
+ if (scan_mgr->push_button_needs_polling ())
+ {
+ long usec = scan_mgr->get_polling_time ();
+ while (!scan_mgr->is_button_pressed ()
+ && !feedback.is_cancelled ()) {
+ g_view_manager->microsleep (usec);
+ while (::gtk_events_pending ()) {
+ ::gtk_main_iteration ();
+ }
+ }
+ if (feedback.is_cancelled ())
+ {
+ return;
+ }
+ scan_mgr->disable_wait_for_button();
+ }
+
+ scan_mgr->init_preview (&width, &height);
+
+ if (!zooming)
+ {
+ change_max_scan_area ( width, height );
+ ::g_view_manager->sensitive ( );
+ }
+
+ resize_preview_window ( width, height );
+ clear_image ( );
+
+ while ( ::gtk_events_pending ( ) )
+ ::gtk_main_iteration ( );
+
+ for (int i = 0; i < height; i++ )
+ {
+ scan_mgr->acquire_image ( m_img_org + i * g_prev_max_x * 3,
+ width * 3,
+ 1,
+ cancel );
+
+ if ( i == 0 )
+ feedback.set_text (progress_window::PREVIEWING);
+
+ if (cancel)
+ break;
+
+ feedback.set_progress (i, height);
+ cancel = feedback.is_cancelled ();
+
+ ::gtk_preview_draw_row ( GTK_PREVIEW ( m_prev ),
+ m_img_org + i * g_prev_max_x * 3,
+ 0, i, width );
+ if ( i % 20 == 0 )
+ {
+ ::gtk_preview_put ( GTK_PREVIEW ( m_prev ),
+ m_prev->window,
+ m_prev->style->black_gc,
+ 0, 0, 0, 0, width, height );
+ }
+
+ while ( ::gtk_events_pending ( ) )
+ ::gtk_main_iteration ( );
+ }
+ scan_mgr->acquire_image (0, 1, 1, cancel);
+ feedback.set_progress (height, height);
+ cancel = feedback.is_cancelled ();
+ if (cancel)
+ {
+ m_on_preview = false;
+ update_img ( );
+ change_max_disp_area ( m_allocate_width, m_allocate_height );
+ m_allocate_width = 0;
+ m_allocate_height = 0;
+ }
+ else
+ {
+ m_is_prev = 1;
+ ::gtk_widget_draw ( m_prev, 0 );
+ ::gdk_flush ( );
+
+ m_on_preview = false;
+
+ auto_exposure ( );
+ update_img ( );
+ change_max_disp_area ( m_allocate_width, m_allocate_height );
+ m_allocate_width = 0;
+ m_allocate_height = 0;
+ }
+ }
+ catch ( const pisa_error & err )
+ {
+ main_window * main_cls;
+ aleart_dialog aleart_dlg;
+
+ if ( PISA_STATUS_CANCELLED != err.get_error_id() )
+ {
+ main_cls = ( main_window * ) ::g_view_manager->get_window_cls ( ID_WINDOW_MAIN );
+ aleart_dlg.message_box ( main_cls->get_widget ( ),
+ err.get_error_string ( ) );
+ }
+
+ delete_marquee ( );
+
+ m_on_preview = false;
+ update_img ( );
+ change_max_disp_area ( m_allocate_width, m_allocate_height );
+ m_allocate_width = 0;
+ m_allocate_height = 0;
+ }
+
+ revert_resolution ();
+
+ scan_mgr->release_memory ();
+ scan_mgr->finish_acquire (true);
+
+ ::g_view_manager->sensitive ( );
+
+ {
+ gint x, y;
+ ::gdk_window_get_pointer ( m_prev->window, & x, & y, 0 );
+ set_mouse_cursor ( x, y );
+ change_cursor ( );
+ }
+}
+
+/*--------------------------------------------------------------*/
+gint preview_window::expose_event ( GtkWidget * widget,
+ GdkEventExpose * event )
+{
+ widget = widget;
+ event = event;
+
+ draw_marquee ( );
+
+ return FALSE;
+}
+
+/*--------------------------------------------------------------*/
+gint preview_window::event ( GtkWidget * widget, GdkEvent * event )
+{
+ gint ret;
+
+ widget = widget;
+
+ switch ( event->type )
+ {
+ case GDK_EXPOSE:
+ if ( m_gc )
+ {
+ draw_marquee ( );
+ }
+ ret = FALSE;
+ break;
+
+ case GDK_BUTTON_PRESS:
+ ret = mouse_down ( event );
+ ::gtk_grab_add ( widget );
+ break;
+
+ case GDK_MOTION_NOTIFY:
+ ret = mouse_move ( event );
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ ret = mouse_up ( event );
+ ::gtk_grab_remove ( widget );
+ break;
+
+ default:
+ ret = TRUE;
+ break;
+ }
+
+ return ret;
+}
+
+/*------------------------------------------------------------*/
+void preview_window::size_allocate ( GtkWidget * widget )
+{
+ int x, y;
+
+ x = widget->allocation.width;
+ y = widget->allocation.height;
+
+ if ( m_on_preview )
+ {
+ m_allocate_width = x;
+ m_allocate_height = y;
+ return;
+ }
+
+ change_max_disp_area ( x, y );
+}
+
+
+/*------------------------------------------------------------*/
+GtkWidget * preview_window::create_darea ( GtkWidget * parent )
+{
+ GtkWidget * frame;
+
+ parent = parent;
+
+ frame = ::gtk_frame_new ( 0 );
+ ::gtk_frame_set_shadow_type ( GTK_FRAME ( frame ), GTK_SHADOW_IN );
+ ::gtk_container_border_width ( GTK_CONTAINER ( frame ), 3 );
+
+ m_prev = ::gtk_preview_new ( GTK_PREVIEW_COLOR );
+ ::gtk_preview_set_expand ( GTK_PREVIEW ( m_prev ), TRUE );
+
+ // set size of preview window
+ get_preview_resolution ( );
+ resize_preview_window ( m_img_width, m_img_height );
+
+ ::gtk_container_add ( GTK_CONTAINER ( frame ), m_prev );
+ ::gtk_widget_show ( m_prev );
+
+ clear_image ( );
+
+ // signals
+ ::gtk_signal_connect ( GTK_OBJECT ( m_prev ), "event",
+ GTK_SIGNAL_FUNC ( ::event ), 0 );
+ ::gtk_signal_connect_after ( GTK_OBJECT ( m_prev ), "expose_event",
+ GTK_SIGNAL_FUNC ( ::expose_event ), 0 );
+ ::gtk_signal_connect_after ( GTK_OBJECT ( m_prev ), "size_allocate",
+ GTK_SIGNAL_FUNC ( ::size_allocate ), 0 );
+
+ ::gtk_widget_set_events ( m_prev ,
+ GDK_EXPOSURE_MASK |
+ GDK_LEAVE_NOTIFY_MASK |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_POINTER_MOTION_MASK |
+ GDK_POINTER_MOTION_HINT_MASK |
+ GDK_BUTTON_RELEASE_MASK );
+
+ return frame;
+}
+
+
+/*------------------------------------------------------------*/
+long preview_window::get_preview_resolution ( const _rectD * img_rect )
+{
+ long resolution;
+ _pointD pt_scan_size;
+ double aspect_max;
+ double aspect_scan;
+
+ if ( img_rect )
+ {
+ pt_scan_size.x = img_rect->right - img_rect->left;
+ pt_scan_size.y = img_rect->bottom - img_rect->top;
+ m_img_rect = * img_rect;
+ }
+ else
+ {
+ pt_scan_size.x = g_view_manager->get_settings ().max_area [ 0 ];
+ pt_scan_size.y = g_view_manager->get_settings ().max_area [ 1 ];
+
+ m_img_rect.left = 0.0;
+ m_img_rect.top = 0.0;
+ m_img_rect.right = pt_scan_size.x;
+ m_img_rect.bottom = pt_scan_size.y;
+ }
+
+ aspect_max = ( float ) g_prev_max_y / g_prev_max_x;
+ aspect_scan = ( float ) pt_scan_size.y / pt_scan_size.x;
+
+ if ( aspect_max < aspect_scan )
+ resolution = ( long ) ( g_prev_max_y / pt_scan_size.y );
+ else
+ resolution = ( long ) ( g_prev_max_x / pt_scan_size.x );
+
+ m_img_width = ( long ) ( resolution * pt_scan_size.x );
+ m_img_height = ( long ) ( resolution * pt_scan_size.y );
+
+ return resolution;
+}
+
+/*------------------------------------------------------------*/
+void preview_window::resize_preview_window ( long width, long height )
+{
+ if ( width != 0 && height != 0 )
+ {
+ m_img_width = width;
+ m_img_height = height;
+ }
+
+ if ( m_img )
+ {
+ delete [ ] m_img;
+ m_img = new unsigned char [ g_prev_max_x * g_prev_max_y * 3 ];
+ }
+
+ if ( m_img_org )
+ {
+ delete [ ] m_img_org;
+ m_img_org = new unsigned char [ g_prev_max_x * g_prev_max_y * 3 ];
+ }
+
+ m_client_rect.left = 0;
+ m_client_rect.top = 0;
+ m_client_rect.right = m_img_width - 1;
+ m_client_rect.bottom = m_img_height - 1;
+
+ modify_max_val ( );
+}
+
+/*------------------------------------------------------------*/
+void preview_window::change_max_scan_area ( long width, long height )
+{
+ _pointD pt_scan_size;
+ marquee * marq;
+
+ if ( m_img_width < width || m_img_height < height )
+ return;
+
+ marq = & g_view_manager->get_marquee ();
+
+ pt_scan_size.x = g_view_manager->get_settings ().max_area [ 0 ];
+ pt_scan_size.y = g_view_manager->get_settings ().max_area [ 1 ];
+
+ m_img_rect.left = 0.0;
+ m_img_rect.top = 0.0;
+ m_img_rect.right = pt_scan_size.x * width / m_img_width;
+ m_img_rect.bottom = pt_scan_size.y * height / m_img_height;
+
+ marq->area.x = m_img_rect.right;
+ marq->area.y = m_img_rect.bottom;
+}
+
+/*------------------------------------------------------------*/
+void preview_window::change_max_disp_area ( long width, long height )
+{
+ long save_prev_x, save_prev_y;
+ long save_img_width, save_img_height;
+ unsigned char * save_img_org;
+ _rectD tmp_rect;
+
+ if ( width < 1 && height < 1 )
+ return;
+
+ if ( g_prev_max_x == width && g_prev_max_y == height )
+ return;
+
+ tmp_rect = m_img_rect;
+
+ if ( m_is_prev == 0 )
+ {
+ g_prev_max_x = width;
+ g_prev_max_y = height;
+
+ get_preview_resolution ( & tmp_rect );
+ resize_preview_window ( 0, 0 );
+
+ clear_image ( );
+
+ return;
+ }
+
+ // save old parameter and image
+ save_prev_x = g_prev_max_x;
+ save_prev_y = g_prev_max_y;
+ save_img_width = m_img_width;
+ save_img_height = m_img_height;
+ save_img_org = new unsigned char [ save_prev_x * save_prev_y * 3 ];
+
+ if ( save_img_org )
+ {
+ ::memcpy ( save_img_org, m_img_org, save_prev_x * save_prev_y * 3 );
+ }
+
+ // resize
+ g_prev_max_x = width;
+ g_prev_max_y = height;
+
+ get_preview_resolution ( & tmp_rect );
+ resize_preview_window ( 0, 0 );
+
+ clear_image ( );
+ m_is_prev = 1;
+
+ // restore image
+ if ( save_img_org )
+ {
+ struct resize_img_info parms;
+
+ parms.in_width = save_img_width;
+ parms.in_height = save_img_height;
+ parms.in_rowbytes = save_prev_x * 3;
+ parms.out_width = m_img_width;
+ parms.out_height = m_img_height;
+ parms.out_rowbytes = g_prev_max_x * 3;
+ parms.bits_per_pixel = 24;
+ parms.resize_flag = PISA_RS_BL;
+ parms.resolution = 0; // just to make sure it's set
+
+ iscan::scale f (parms);
+
+ f.exec (save_img_org, parms.in_height * parms.in_rowbytes,
+ m_img_org, parms.out_height * parms.out_rowbytes);
+
+ delete [] save_img_org;
+ }
+
+ update_img ( );
+}
+
+/*------------------------------------------------------------*/
+void preview_window::clear_image ( void )
+{
+ int i;
+
+ m_is_prev = 0;
+
+ ::memset ( m_img, 0xff, m_img_width * 3 );
+ ::memset ( m_img + ( m_img_width * 3 ),
+ 0xC4,
+ ( g_prev_max_x - m_img_width ) * 3 );
+
+ for ( i = 0; i < m_img_height; i++ )
+ ::gtk_preview_draw_row ( GTK_PREVIEW ( m_prev ), m_img,
+ 0, i, g_prev_max_x );
+
+ ::memset ( m_img, 0xC4, g_prev_max_x * 3 );
+
+ for ( ; i < g_prev_max_y; i++ )
+ ::gtk_preview_draw_row ( GTK_PREVIEW ( m_prev ), m_img,
+ 0, i, g_prev_max_x );
+
+ ::gtk_widget_draw ( m_prev, 0 );
+ ::gdk_flush ( );
+}
+
+
+/*--------------------------------------------------------------*/
+void preview_window::draw_marquee ( void )
+{
+ long i, marq_num;
+ _pointL pt_lefttop, pt_rightbottom;
+
+ if ( m_gc == 0 )
+ {
+ m_gc = ::gdk_gc_new ( m_prev->window );
+ ::gdk_gc_set_function ( m_gc, GDK_INVERT );
+ ::gdk_gc_set_line_attributes ( m_gc, 1, GDK_LINE_ON_OFF_DASH,
+ GDK_CAP_BUTT, GDK_JOIN_MITER );
+ }
+
+ marq_num = g_view_manager->get_settings ().get_marquee_size ( );
+
+ if ( marq_num < 2 || m_on_preview )
+ return;
+
+ for ( i = 1; i < marq_num; i++ )
+ {
+ get_marquee_point ( i, & pt_lefttop, & pt_rightbottom );
+
+ draw_rect ( pt_lefttop, pt_rightbottom );
+ }
+}
+
+/*--------------------------------------------------------------*/
+void
+preview_window::reset_settings (bool zooming)
+{
+ long i, n, m, marq_num;
+ marquee * marq;
+ gamma_correction * gamma_cls;
+ scan_manager * scan_mgr;
+
+ gamma_cls = g_view_manager->get_gamma_correction ();
+
+ scan_mgr = g_view_manager->get_scan_manager ();
+ marq_num = g_view_manager->get_marquee_size ();
+
+ if (!zooming)
+ {
+ while ( 1 < marq_num )
+ {
+ g_view_manager->del_marquee (marq_num - 1);
+ marq_num = g_view_manager->get_marquee_size ();
+ }
+
+ gamma_cls->reset ( 1 );
+ }
+
+ marq_num = g_view_manager->get_marquee_size ();
+
+ for ( i = 0; i < marq_num; i++ )
+ {
+ marq = & g_view_manager->get_marquee (i);
+
+ if (!zooming)
+ {
+ marq->gamma = ( long ) ( 100 * DEFGAMMA );
+ marq->highlight = DEFHIGHLIGHT;
+ marq->shadow = DEFSHADOW;
+ marq->threshold = DEFTHRESHOLD;
+
+ for ( n = 0; n < 4; n++ )
+ for ( m = 0; m < 256; m++ )
+ marq->gamma_table [ n ] [ m ] = m;
+
+ marq->graybalance = DEFGRAYBALANCE;
+ marq->saturation = DEFSATURATION;
+ }
+
+ for ( n = 0; n < 3; n++ )
+ {
+ marq->film_gamma [ n ] = 1.0;
+ marq->film_yp [ n ] = 0.0;
+ marq->grayl [ n ] = 0.0;
+ }
+
+ iscan::build_LUT (scan_mgr->get_scan_source (),
+ scan_mgr->get_film_type (),
+ g_view_manager->get_settings (),
+ *marq, !scan_mgr->has_zoom ());
+ }
+}
+
+int
+preview_window::set_preview_param (bool zooming)
+{
+ settings set = g_view_manager->get_settings ();
+ marquee marq;
+
+ if (!zooming)
+ {
+ marq.offset.x = 0.0;
+ marq.offset.y = 0.0;
+ marq.area.x = set.max_area[0];
+ marq.area.y = set.max_area[1];
+ }
+ else
+ {
+ float rate = 0.1f;
+ _pointD pt_offset, pt_area, pt_max;
+
+ marquee cur_marq = g_view_manager->get_marquee ();
+
+ marq.focus = cur_marq.focus;
+
+ pt_offset = cur_marq.offset;
+ pt_area = cur_marq.area;
+
+ marquee whole_marq = g_view_manager->get_marquee (0);
+
+ pt_max.x = whole_marq.area.x;
+ pt_max.y = whole_marq.area.y;
+
+ zoom_boundary (pt_offset, pt_area, pt_max, rate);
+
+ marq.offset.x = pt_offset.x;
+ marq.offset.y = pt_offset.y;
+ marq.area.x = pt_area.x;
+ marq.area.y = pt_area.y;
+ }
+
+ _rectD img_rect;
+ img_rect.left = marq.offset.x;
+ img_rect.top = marq.offset.y;
+ img_rect.right = marq.offset.x + marq.area.x;
+ img_rect.bottom = marq.offset.y + marq.area.y;
+
+ long resolution = get_preview_resolution (&img_rect);
+
+ pisa_error_id err = (g_view_manager->get_scan_manager ()
+ ->set_scan_parameters (set, marq, resolution));
+
+ return err;
+}
+
+void
+preview_window::revert_resolution ( void )
+{
+ long set_res = g_view_manager->get_settings ().resolution;
+
+ g_view_manager->set_resolution (set_res);
+}
+
+/*------------------------------------------------------------*/
+void preview_window::tool_usm (const pisa_image_info& info)
+{
+ iscan::focus f (info);
+
+ f.exec (info.m_img, info.m_height * info.m_rowbytes,
+ info.m_img, info.m_height * info.m_rowbytes);
+}
+
+void
+preview_window::zoom_boundary (_pointD& pt_offset, _pointD& pt_area,
+ const _pointD& pt_max, float rate) const
+{
+ _pointD pt_result_offset, pt_result_area;
+ double bound_size;
+
+ if (pt_area.x < pt_area.y)
+ bound_size = pt_area.y * rate;
+ else
+ bound_size = pt_area.x * rate;
+
+ bound_size = (0.0 == bound_size) ? 0.01 : bound_size;
+
+ // left
+ pt_result_offset.x = pt_offset.x - bound_size;
+ if (0.0 > pt_result_offset.x)
+ pt_result_offset.x = 0.0;
+
+ // top
+ pt_result_offset.y = pt_offset.y - bound_size;
+ if (0.0 > pt_result_offset.y)
+ pt_result_offset.y = 0.0;
+
+ // right
+ pt_result_area.x = ((pt_offset.x - pt_result_offset.x) +
+ pt_area.x + bound_size);
+ if (pt_max.x < pt_result_offset.x + pt_result_area.x)
+ pt_result_area.x = pt_max.x - pt_result_offset.x;
+
+ // bottom
+ pt_result_area.y = ((pt_offset.y - pt_result_offset.y) +
+ pt_area.y + bound_size);
+ if (pt_max.y < pt_result_offset.y + pt_result_area.y)
+ pt_result_area.y = pt_max.y - pt_result_offset.y;
+
+ pt_offset = pt_result_offset;
+ pt_area = pt_result_area;
+}
+
+/*--------------------------------------------------------------*/
+void preview_window::create_cursor ( void )
+{
+ m_cursor [ PISA_CS_ARROW ] = ::gdk_cursor_new ( GDK_ARROW );
+ m_cursor [ PISA_CS_HAND ] = ::gdk_cursor_new ( GDK_HAND2 );
+ m_cursor [ PISA_CS_CROSS ] = ::gdk_cursor_new ( GDK_CROSS );
+ m_cursor [ PISA_CS_TOP ] = ::gdk_cursor_new ( GDK_TOP_SIDE );
+ m_cursor [ PISA_CS_BOTTOM ] = ::gdk_cursor_new ( GDK_BOTTOM_SIDE );
+ m_cursor [ PISA_CS_LEFT ] = ::gdk_cursor_new ( GDK_LEFT_SIDE );
+ m_cursor [ PISA_CS_RIGHT ] = ::gdk_cursor_new ( GDK_RIGHT_SIDE );
+ m_cursor [ PISA_CS_LEFTTOP ] = ::gdk_cursor_new ( GDK_TOP_LEFT_CORNER );
+ m_cursor [ PISA_CS_LEFTBOTTOM ] = ::gdk_cursor_new ( GDK_BOTTOM_LEFT_CORNER );
+ m_cursor [ PISA_CS_RIGHTTOP ] = ::gdk_cursor_new ( GDK_TOP_RIGHT_CORNER );
+ m_cursor [ PISA_CS_RIGHTBOTTOM ] = ::gdk_cursor_new ( GDK_BOTTOM_RIGHT_CORNER );
+}
+
+
+/*--------------------------------------------------------------*/
+int preview_window::set_mouse_cursor ( int x, int y )
+{
+ const long ADDSIZE = 3;
+ long marq_num;
+ _pointL pt_lefttop, pt_rightbottom;
+ int cursor_state;
+ _pointL pt;
+ int pt_in_cur_marq;
+ _rectL judge_rect;
+ _pointL pt_lt, pt_rb;
+ int i;
+
+ if ( m_drag == 1 )
+ return FALSE;
+
+ pt.x = x;
+ pt.y = y;
+
+ if ( 0 == ::pt_in_rect ( m_client_rect, pt ) )
+ {
+ m_cursor_state = PISA_CS_ARROW;
+ return FALSE;
+ }
+
+ marq_num = g_view_manager->get_settings ().get_marquee_size ( );
+
+ if ( marq_num < 2 )
+ {
+ m_cursor_state = PISA_CS_CROSS;
+ return FALSE;
+ }
+
+ get_marquee_point ( marq_num - 1, & pt_lefttop, & pt_rightbottom );
+
+ cursor_state = search_cursor_state ( pt_lefttop, pt_rightbottom, pt );
+ if ( cursor_state != PISA_CS_CROSS )
+ {
+ m_cursor_state = cursor_state;
+ return FALSE;
+ }
+
+ judge_rect.left = pt_lefttop.x;
+ judge_rect.right = pt_rightbottom.x;
+ judge_rect.top = pt_lefttop.y;
+ judge_rect.bottom = pt_rightbottom.y;
+ if ( pt_in_rect ( judge_rect, pt ) )
+ pt_in_cur_marq = 1;
+ else
+ pt_in_cur_marq = 0;
+
+ for ( i = marq_num - 1; 0 < i; i-- )
+ {
+ if ( marq_num - 1 == i )
+ continue;
+
+ get_marquee_point ( i, & pt_lt, & pt_rb );
+
+ if ( pt_in_cur_marq )
+ {
+ cursor_state = search_cursor_state ( pt_lt, pt_rb, pt );
+ if ( PISA_CS_CROSS != cursor_state )
+ {
+ m_cursor_state = PISA_CS_ARROW;
+ return i;
+ }
+ }
+ else
+ {
+ judge_rect.left = pt_lt.x - ADDSIZE;
+ judge_rect.right = pt_rb.x + ADDSIZE;
+ judge_rect.top = pt_lt.y - ADDSIZE;
+ judge_rect.bottom = pt_rb.y + ADDSIZE;
+ if ( pt_in_rect ( judge_rect, pt ) )
+ {
+ m_cursor_state = PISA_CS_ARROW;
+ return i;
+ }
+ }
+ }
+
+ if ( pt_in_cur_marq )
+ {
+ m_cursor_state = PISA_CS_HAND;
+ return 0;
+ }
+
+ m_cursor_state = PISA_CS_CROSS;
+
+ return 0;
+}
+
+/*--------------------------------------------------------------*/
+int preview_window::search_cursor_state ( const _pointL & pt_lt,
+ const _pointL & pt_rb,
+ const _pointL & pt )
+{
+ const long ADDSIZE = 3;
+ _rectL judge_rect;
+
+ // check left top
+ judge_rect.left = pt_lt.x - ADDSIZE;
+ judge_rect.right = pt_lt.x + ADDSIZE;
+ judge_rect.top = pt_lt.y - ADDSIZE;
+ judge_rect.bottom = pt_lt.y + ADDSIZE;
+ if ( ::pt_in_rect ( judge_rect, pt ) )
+ return PISA_CS_LEFTTOP;
+
+ // check right bottom
+ judge_rect.left = pt_rb.x - ADDSIZE;
+ judge_rect.right = pt_rb.x + ADDSIZE;
+ judge_rect.top = pt_rb.y - ADDSIZE;
+ judge_rect.bottom = pt_rb.y + ADDSIZE;
+ if ( ::pt_in_rect ( judge_rect, pt ) )
+ return PISA_CS_RIGHTBOTTOM;
+
+ // check right top
+ judge_rect.left = pt_rb.x - ADDSIZE;
+ judge_rect.right = pt_rb.x + ADDSIZE;
+ judge_rect.top = pt_lt.y - ADDSIZE;
+ judge_rect.bottom = pt_lt.y + ADDSIZE;
+ if ( ::pt_in_rect ( judge_rect, pt ) )
+ return PISA_CS_RIGHTTOP;
+
+ // check left bottom
+ judge_rect.left = pt_lt.x - ADDSIZE;
+ judge_rect.right = pt_lt.x + ADDSIZE;
+ judge_rect.top = pt_rb.y - ADDSIZE;
+ judge_rect.bottom = pt_rb.y + ADDSIZE;
+ if ( ::pt_in_rect ( judge_rect, pt ) )
+ return PISA_CS_LEFTBOTTOM;
+
+ // check left resizing
+ judge_rect.left = pt_lt.x - ADDSIZE;
+ judge_rect.right = pt_lt.x + ADDSIZE;
+ judge_rect.top = pt_lt.y;
+ judge_rect.bottom = pt_rb.y;
+ if ( ::pt_in_rect ( judge_rect, pt ) )
+ return PISA_CS_LEFT;
+
+ // check right resizing
+ judge_rect.left = pt_rb.x - ADDSIZE;
+ judge_rect.right = pt_rb.x + ADDSIZE;
+ judge_rect.top = pt_lt.y;
+ judge_rect.bottom = pt_rb.y;
+ if ( ::pt_in_rect ( judge_rect, pt ) )
+ return PISA_CS_RIGHT;
+
+ // check top resizing
+ judge_rect.left = pt_lt.x;
+ judge_rect.right = pt_rb.x;
+ judge_rect.top = pt_lt.y - ADDSIZE;
+ judge_rect.bottom = pt_lt.y + ADDSIZE;
+ if ( ::pt_in_rect ( judge_rect, pt ) )
+ return PISA_CS_TOP;
+
+ // check bottom resizing
+ judge_rect.left = pt_lt.x;
+ judge_rect.right = pt_rb.x;
+ judge_rect.top = pt_rb.y - ADDSIZE;
+ judge_rect.bottom = pt_rb.y + ADDSIZE;
+ if ( ::pt_in_rect ( judge_rect, pt ) )
+ return PISA_CS_BOTTOM;
+
+ return PISA_CS_CROSS;
+}
+
+/*--------------------------------------------------------------*/
+void preview_window::change_cursor ( void )
+{
+ ::gdk_window_set_cursor ( m_prev->window,
+ m_cursor [ m_cursor_state ] );
+}
+
+/*--------------------------------------------------------------*/
+void preview_window::modify_max_val ( void )
+{
+ marquee * whole_marq;
+ _pointD pt_min_D, pt_max_D;
+
+ whole_marq = & g_view_manager->get_marquee (0);
+ m_max_img_rect.top = 0.0;
+ m_max_img_rect.left = 0.0;
+ m_max_img_rect.right = whole_marq->area.x;
+ m_max_img_rect.bottom = whole_marq->area.y;
+
+ pt_min_D.x = m_max_img_rect.left;
+ pt_min_D.y = m_max_img_rect.top;
+ pt_max_D.x = m_max_img_rect.right;
+ pt_max_D.y = m_max_img_rect.bottom;
+ m_pt_max_rb = inches2clientpix ( pt_max_D );
+ m_pt_max_lt.x = 0;
+ m_pt_max_lt.y = 0;
+ m_pt_max_rb.x = m_img_width - 1;
+ m_pt_max_rb.y = m_img_height - 1;
+}
+
+/*--------------------------------------------------------------*/
+gint preview_window::mouse_down ( GdkEvent * event )
+{
+ marquee * cur_marq;
+ _pointL pt_lefttop, pt_rightbottom;
+ long id_cur_marquee;
+
+ if ( event->button.button != 1 )
+ return TRUE;
+
+ if ( m_img_width - 1 < ( int ) event->button.x || m_img_height - 1 < ( int ) event->button.y )
+ return TRUE;
+
+ m_pt_begin.x = ( int ) event->button.x;
+ m_pt_begin.y = ( int ) event->button.y;
+
+ m_drag = 1;
+
+ m_pt_old = m_pt_begin;
+
+ id_cur_marquee = g_view_manager->get_marquee_size () - 1;
+ get_marquee_point ( id_cur_marquee, & pt_lefttop, & pt_rightbottom );
+
+ cur_marq = & g_view_manager->get_marquee ();
+
+ m_pt_save_offset = cur_marq->offset;
+ m_pt_save_area = cur_marq->area;
+
+ switch ( m_cursor_state )
+ {
+ case PISA_CS_CROSS:
+ m_pt_old_lt = m_pt_old_rb = m_pt_begin;
+ delete_marquee ( );
+ create_marquee ( m_pt_old_lt, m_pt_old_rb );
+ break;
+ case PISA_CS_HAND:
+ case PISA_CS_TOP:
+ case PISA_CS_BOTTOM:
+ case PISA_CS_LEFT:
+ case PISA_CS_RIGHT:
+ case PISA_CS_LEFTTOP:
+ case PISA_CS_LEFTBOTTOM:
+ case PISA_CS_RIGHTTOP:
+ case PISA_CS_RIGHTBOTTOM:
+ m_pt_old_lt = pt_lefttop;
+ m_pt_old_rb = pt_rightbottom;
+ break;
+ }
+
+ return FALSE;
+}
+
+/*--------------------------------------------------------------*/
+gint preview_window::mouse_move ( GdkEvent * event )
+{
+ int x, y;
+ GdkModifierType state;
+ _pointL pt_mouse, pt_move, pt_tmp_lt, pt_tmp_rb, pt_min_L;
+ _pointD pt_lefttop, pt_rightbottom, pt_min_D, pt_zero_D;
+
+ if ( event->motion.is_hint )
+ {
+ ::gdk_window_get_pointer ( m_prev->window, & x, & y, & state );
+ }
+ else
+ return TRUE;
+
+ if ( m_drag == 0 )
+ {
+ set_mouse_cursor ( x, y );
+ change_cursor ( );
+ return FALSE;
+ }
+
+ if ( x < 0 ) x = 0;
+ if ( y < 0 ) y = 0;
+ if ( m_client_rect.right < x ) x = m_client_rect.right;
+ if ( m_client_rect.bottom < y ) y = m_client_rect.bottom;
+
+ pt_mouse.x = x;
+ pt_mouse.y = y;
+
+ // clear previous marquee
+ draw_rect ( m_pt_old_lt, m_pt_old_rb );
+
+ pt_lefttop = m_pt_save_offset;
+ pt_rightbottom = m_pt_save_offset + m_pt_save_area;
+
+ pt_tmp_lt = inches2clientpix ( pt_lefttop );
+ pt_tmp_rb = inches2clientpix ( pt_rightbottom );
+
+ pt_zero_D.x = 0.0;
+ pt_zero_D.y = 0.0;
+ pt_min_D.x = g_min_marq_size;
+ pt_min_D.y = g_min_marq_size;
+ pt_min_L = inches2clientpix ( pt_min_D ) - inches2clientpix ( pt_zero_D );
+
+ pt_move = pt_mouse - m_pt_begin;
+
+ // initialize
+ begin_mouse_move ( & pt_tmp_lt, & pt_tmp_rb );
+
+ move_rect ( & pt_tmp_lt, & pt_tmp_rb, pt_move );
+
+ switch ( m_cursor_state )
+ {
+ case PISA_CS_CROSS:
+ m_pt_old_rb = pt_mouse;
+ break;
+ case PISA_CS_HAND:
+ m_pt_old_rb = pt_tmp_lt + ( m_pt_old_rb - m_pt_old_lt );
+ m_pt_old_lt = pt_tmp_lt;
+ break;
+ case PISA_CS_TOP:
+ if ( m_pt_old_rb.y - pt_min_L.y > pt_tmp_lt.y )
+ m_pt_old_lt.y = pt_tmp_lt.y;
+ else
+ m_pt_old_lt.y = m_pt_old_rb.y - pt_min_L.y;
+ break;
+ case PISA_CS_BOTTOM:
+ if ( m_pt_old_lt.y + pt_min_L.y < pt_tmp_rb.y )
+ m_pt_old_rb.y = pt_tmp_rb.y;
+ else
+ m_pt_old_rb.y = m_pt_old_lt.y + pt_min_L.y;
+ break;
+ case PISA_CS_LEFT:
+ if ( m_pt_old_rb.x - pt_min_L.x > pt_tmp_lt.x )
+ m_pt_old_lt.x = pt_tmp_lt.x;
+ else
+ m_pt_old_lt.x = m_pt_old_rb.x - pt_min_L.x;
+ break;
+ case PISA_CS_RIGHT:
+ if ( m_pt_old_lt.x + pt_min_L.x < pt_tmp_rb.x )
+ m_pt_old_rb.x = pt_tmp_rb.x;
+ else
+ m_pt_old_rb.x = m_pt_old_lt.x + pt_min_L.x;
+ break;
+ case PISA_CS_LEFTTOP:
+ if ( m_pt_old_rb.y - pt_min_L.y > pt_tmp_lt.y )
+ m_pt_old_lt.y = pt_tmp_lt.y;
+ else
+ m_pt_old_lt.y = m_pt_old_rb.y - pt_min_L.y;
+ if ( m_pt_old_rb.x - pt_min_L.x > pt_tmp_lt.x )
+ m_pt_old_lt.x = pt_tmp_lt.x;
+ else
+ m_pt_old_lt.x = m_pt_old_rb.x - pt_min_L.x;
+ break;
+ case PISA_CS_LEFTBOTTOM:
+ if ( m_pt_old_rb.x - pt_min_L.x > pt_tmp_lt.x )
+ m_pt_old_lt.x = pt_tmp_lt.x;
+ else
+ m_pt_old_lt.x = m_pt_old_rb.x - pt_min_L.x;
+ if ( m_pt_old_lt.y + pt_min_L.y < pt_tmp_rb.y )
+ m_pt_old_rb.y = pt_tmp_rb.y;
+ else
+ m_pt_old_rb.y = m_pt_old_lt.y + pt_min_L.y;
+ break;
+ case PISA_CS_RIGHTTOP:
+ if ( m_pt_old_rb.y - pt_min_L.y > pt_tmp_lt.y )
+ m_pt_old_lt.y = pt_tmp_lt.y;
+ else
+ m_pt_old_lt.y = m_pt_old_rb.y - pt_min_L.y;
+ if ( m_pt_old_lt.x + pt_min_L.x < pt_tmp_rb.x )
+ m_pt_old_rb.x = pt_tmp_rb.x;
+ else
+ m_pt_old_rb.x = m_pt_old_lt.x + pt_min_L.x;
+ break;
+ case PISA_CS_RIGHTBOTTOM:
+ if ( m_pt_old_lt.y + pt_min_L.y < pt_tmp_rb.y )
+ m_pt_old_rb.y = pt_tmp_rb.y;
+ else
+ m_pt_old_rb.y = m_pt_old_lt.y + pt_min_L.y;
+ if ( m_pt_old_lt.x + pt_min_L.x < pt_tmp_rb.x )
+ m_pt_old_rb.x = pt_tmp_rb.x;
+ else
+ m_pt_old_rb.x = m_pt_old_lt.x + pt_min_L.x;
+ break;
+ }
+
+ draw_rect ( m_pt_old_lt, m_pt_old_rb );
+
+ if ( m_cursor_state != PISA_CS_HAND )
+ resize_marquee ( m_pt_old_lt, m_pt_old_rb );
+
+ m_pt_old = pt_mouse;
+
+ return FALSE;
+}
+
+/*--------------------------------------------------------------*/
+gint preview_window::mouse_up ( GdkEvent * event )
+{
+ int min_check;
+ _pointL pt_mouse;
+ long marq_num;
+
+ if ( 0 == m_drag )
+ return FALSE;
+
+ pt_mouse.x = ( int ) event->button.x;
+ pt_mouse.y = ( int ) event->button.y;
+
+ // clear previous marquee
+ marq_num = g_view_manager->get_marquee_size ();
+ if ( 1 < marq_num )
+ draw_rect ( m_pt_old_lt, m_pt_old_rb );
+
+ switch ( m_cursor_state )
+ {
+ case PISA_CS_CROSS:
+ min_check = check_min_size ( m_pt_old_lt, m_pt_old_rb );
+ if ( min_check == 0 )
+ delete_marquee ( );
+ update_img ( );
+ break;
+ case PISA_CS_ARROW:
+ break;
+ case PISA_CS_HAND:
+ move_marquee ( m_pt_old_lt, m_pt_old_rb );
+ update_img ( );
+ break;
+ case PISA_CS_LEFT:
+ case PISA_CS_LEFTTOP:
+ case PISA_CS_LEFTBOTTOM:
+ case PISA_CS_RIGHT:
+ case PISA_CS_RIGHTTOP:
+ case PISA_CS_RIGHTBOTTOM:
+ case PISA_CS_TOP:
+ case PISA_CS_BOTTOM:
+ if ( 0 == check_min_size ( m_pt_old_lt, m_pt_old_rb ) )
+ delete_marquee ( );
+ update_img ( PISA_CS_LEFT == m_cursor_state
+ || PISA_CS_LEFTTOP == m_cursor_state
+ || PISA_CS_LEFTBOTTOM == m_cursor_state);
+ break;
+ }
+
+ m_drag = 0;
+
+ set_mouse_cursor ( pt_mouse.x, pt_mouse.y );
+ change_cursor ( );
+
+ return FALSE;
+}
+
+/*--------------------------------------------------------------*/
+int preview_window::create_marquee ( const _pointL & pt_lt, const _pointL & pt_rb )
+{
+ _pointL pt_lefttop_L, pt_rightbottom_L;
+ _pointD pt_lefttop_D, pt_rightbottom_D, pt_area_D;
+ marquee * new_marq, * whole_marq;
+
+ pt_lefttop_L = pt_lt;
+ pt_rightbottom_L = pt_rb;
+ check_ltrb ( & pt_lefttop_L, & pt_rightbottom_L );
+
+ pt_lefttop_D = clientpix2inches ( pt_lefttop_L );
+ pt_rightbottom_D = clientpix2inches ( pt_rightbottom_L );
+ pt_area_D = pt_rightbottom_D - pt_lefttop_D;
+
+ check_max_size ( & pt_lefttop_D, & pt_area_D, 0 );
+
+ whole_marq = & g_view_manager->get_marquee (0);
+ new_marq = new marquee;
+ * new_marq = * whole_marq;
+
+ new_marq->offset = pt_lefttop_D;
+ new_marq->area = pt_area_D;
+
+ g_view_manager->add_marquee (&new_marq);
+
+ ::g_view_manager->sensitive ( );
+
+ return 1;
+}
+
+/*--------------------------------------------------------------*/
+int preview_window::delete_marquee ( void )
+{
+ long num_marq;
+ int i, j;
+ marquee * cur_marq, * whole_marq;
+
+ num_marq = g_view_manager->get_marquee_size ();
+
+ // no marquee
+ if ( num_marq < 2 )
+ return PISA_ERR_SUCCESS;
+
+ whole_marq = & g_view_manager->get_marquee (0);
+ cur_marq = & g_view_manager->get_marquee ();
+
+ whole_marq->gamma = cur_marq->gamma;
+ whole_marq->highlight = cur_marq->highlight;
+ whole_marq->shadow = cur_marq->shadow;
+ whole_marq->threshold = cur_marq->threshold;
+
+ for ( i = 0; i < 4; i++ )
+ for ( j = 0; j < 256; j++ )
+ whole_marq->gamma_table [ i ] [ j ] = cur_marq->gamma_table [ i ] [ j ];
+
+ whole_marq->graybalance = cur_marq->graybalance;
+ whole_marq->saturation = cur_marq->saturation;
+
+ whole_marq->scale = cur_marq->scale;
+
+ whole_marq->focus = cur_marq->focus;
+
+ for ( i = 0; i < 3; i++ )
+ {
+ whole_marq->film_gamma [ i ] = cur_marq->film_gamma [ i ];
+ whole_marq->film_yp [ i ] = cur_marq->film_yp [ i ];
+ whole_marq->grayl [ i ] = cur_marq->grayl [ i ];
+ }
+
+ for ( i = 0; i < 256; i++ )
+ {
+ whole_marq->lut.gamma_r [ i ] = cur_marq->lut.gamma_r [ i ];
+ whole_marq->lut.gamma_g [ i ] = cur_marq->lut.gamma_g [ i ];
+ whole_marq->lut.gamma_b [ i ] = cur_marq->lut.gamma_b [ i ];
+ }
+
+ g_view_manager->del_marquee ();
+
+ ::g_view_manager->sensitive ( );
+ update_img ( );
+
+ return PISA_ERR_SUCCESS;
+}
+
+/*--------------------------------------------------------------*/
+void preview_window::move_marquee ( const _pointL & pt_lt,
+ const _pointL & pt_rb )
+{
+ _pointL pt_lefttop_L, pt_rightbottom_L, pt_tmp;
+ _pointD pt_lefttop_D, pt_rightbottom_D, pt_offset, pt_area;
+ marquee * cur_marq;
+
+ pt_tmp = pt_rb;
+
+ pt_lefttop_L = pt_lt;
+ pt_lefttop_D = clientpix2inches ( pt_lefttop_L );
+ pt_rightbottom_D = clientpix2inches ( pt_rightbottom_L );
+
+ pt_offset = pt_lefttop_D;
+ pt_area = pt_rightbottom_D - pt_lefttop_D;
+
+ check_max_size ( & pt_offset, & pt_area, 1 );
+
+ cur_marq = & g_view_manager->get_marquee ();
+
+ cur_marq->offset = pt_offset;
+
+ ::g_view_manager->sensitive ( );
+}
+
+/*--------------------------------------------------------------*/
+void preview_window::resize_marquee ( const _pointL & pt_lt,
+ const _pointL & pt_rb )
+{
+ _pointL pt_lefttop_L, pt_rightbottom_L;
+ _pointD pt_lefttop_D, pt_rightbottom_D, pt_offset, pt_area;
+ marquee * cur_marq;
+
+ pt_lefttop_L = pt_lt;
+ pt_rightbottom_L = pt_rb;
+
+ check_ltrb ( & pt_lefttop_L, & pt_rightbottom_L );
+
+ pt_lefttop_D = clientpix2inches ( pt_lefttop_L );
+ pt_rightbottom_D = clientpix2inches ( pt_rightbottom_L );
+
+ pt_offset = pt_lefttop_D;
+ pt_area = pt_rightbottom_D - pt_lefttop_D;
+
+ check_max_size ( & pt_offset, & pt_area, 0 );
+
+ cur_marq = & g_view_manager->get_marquee ();
+
+ cur_marq->offset = pt_offset;
+ cur_marq->area = pt_area;
+
+ ::g_view_manager->sensitive ( );
+}
+
+/*--------------------------------------------------------------*/
+void preview_window::begin_mouse_move ( _pointL * pt_lt, _pointL * pt_rb )
+{
+ switch ( m_cursor_state )
+ {
+ case PISA_CS_CROSS:
+ case PISA_CS_HAND:
+ break;
+ case PISA_CS_TOP:
+ pt_lt->x = pt_rb->x;
+ pt_rb->y = pt_lt->y;
+ break;
+ case PISA_CS_BOTTOM:
+ * pt_lt = * pt_rb;
+ break;
+ case PISA_CS_LEFT:
+ pt_lt->y = pt_rb->y;
+ pt_rb->x = pt_lt->x;
+ break;
+ case PISA_CS_RIGHT:
+ * pt_lt = * pt_rb;
+ break;
+ case PISA_CS_LEFTTOP:
+ * pt_rb = * pt_lt;
+ break;
+ case PISA_CS_LEFTBOTTOM:
+ pt_lt->y = pt_rb->y;
+ pt_rb->x = pt_lt->x;
+ break;
+ case PISA_CS_RIGHTBOTTOM:
+ * pt_lt = * pt_rb;
+ break;
+ case PISA_CS_RIGHTTOP:
+ pt_lt->x = pt_rb->x;
+ pt_rb->y = pt_lt->y;
+ break;
+ }
+}
+
+/*--------------------------------------------------------------*/
+void preview_window::move_rect ( _pointL * pt_lt, _pointL * pt_rb,
+ const _pointL & pt_move )
+{
+ // move direction
+ typedef enum { DIR_LT, DIR_LB, DIR_RT, DIR_RB } MOVE_DIR;
+ MOVE_DIR direction;
+ _pointL pt_tmp, pt_tmp_move ( pt_move );
+ _pointL pt_size;
+ _pointD pt_lefttop_D, pt_rightbottom_D;
+ _pointL pt_lefttop_L, pt_rightbottom_L;
+
+ if ( pt_move.x < 0 )
+ {
+ if ( pt_move.y < 0 )
+ direction = DIR_LT;
+ else
+ direction = DIR_LB;
+ }
+ else
+ {
+ if ( pt_move.y < 0 )
+ direction = DIR_RT;
+ else
+ direction = DIR_RB;
+ }
+
+ pt_lefttop_D = m_pt_save_offset;
+ pt_rightbottom_D = m_pt_save_offset + m_pt_save_area;
+
+ pt_lefttop_L = inches2clientpix ( pt_lefttop_D );
+ pt_rightbottom_L = inches2clientpix ( pt_rightbottom_D );
+
+ pt_size.x = pt_rightbottom_L.x - pt_lefttop_L.x + 1;
+ pt_size.y = pt_rightbottom_L.y - pt_lefttop_L.y + 1;
+
+ switch ( direction )
+ {
+ case DIR_LT:
+ pt_tmp.x = pt_lt->x + pt_tmp_move.x;
+ if ( pt_tmp.x < m_pt_max_lt.x )
+ pt_tmp.x = m_pt_max_lt.x;
+ pt_tmp.y = pt_lt->y + pt_tmp_move.y;
+ if ( pt_tmp.y < m_pt_max_lt.y )
+ pt_tmp.y = m_pt_max_lt.y;
+ * pt_rb = pt_tmp + ( * pt_rb - * pt_lt );
+ * pt_lt = pt_tmp;
+ break;
+ case DIR_LB:
+ pt_tmp.x = pt_lt->x + pt_tmp_move.x;
+ if ( pt_tmp.x < m_pt_max_lt.x )
+ pt_tmp.x = m_pt_max_lt.x;
+ pt_tmp.y = pt_rb->y + pt_tmp_move.y;
+ if ( m_pt_max_rb.y < pt_tmp.y )
+ pt_tmp.y = m_pt_max_rb.y;
+ pt_lt->y = pt_tmp.y - ( pt_rb->y - pt_lt->y );
+ pt_rb->x = pt_tmp.x - ( pt_rb->x - pt_lt->x );
+ pt_lt->x = pt_tmp.x;
+ pt_rb->y = pt_tmp.y;
+ break;
+ case DIR_RT:
+ pt_tmp.x = pt_rb->x + pt_tmp_move.x;
+ if ( m_pt_max_rb.x < pt_tmp.x )
+ pt_tmp.x = m_pt_max_rb.x;
+ pt_tmp.y = pt_lt->y + pt_tmp_move.y;
+ if ( pt_tmp.y < m_pt_max_lt.y )
+ pt_tmp.y = m_pt_max_lt.y;
+ pt_lt->x = pt_tmp.x - ( pt_rb->x - pt_lt->x );
+ pt_rb->y = pt_tmp.y + ( pt_rb->y - pt_lt->y );
+ pt_lt->y = pt_tmp.y;
+ pt_rb->x = pt_tmp.x;
+ break;
+ case DIR_RB:
+ pt_tmp.x = pt_rb->x + pt_tmp_move.x;
+ if ( m_pt_max_rb.x < pt_tmp.x )
+ pt_tmp.x = m_pt_max_rb.x;
+ pt_tmp.y = pt_rb->y + pt_tmp_move.y;
+ if ( m_pt_max_rb.y < pt_tmp.y )
+ pt_tmp.y = m_pt_max_rb.y;
+ * pt_lt = pt_tmp - ( * pt_rb - * pt_lt );
+ * pt_rb = pt_tmp;
+ break;
+ }
+}
+
+/*--------------------------------------------------------------*/
+_pointD preview_window::clientpix2inches ( _pointL & pt )
+{
+ _pointD pt_result;
+ _pointD pt_tmp;
+ double da, db;
+
+ da = m_client_rect.right - m_client_rect.left;
+ db = m_img_rect.right - m_img_rect.left;
+ pt_tmp.x = similarity ( ( double ) pt.x, da, db );
+
+ da = m_client_rect.bottom - m_client_rect.top;
+ db = m_img_rect.bottom - m_img_rect.top;
+ pt_tmp.y = similarity ( ( double ) pt.y, da, db );
+
+
+ pt_result.x = m_img_rect.left + pt_tmp.x;
+ pt_result.y = m_img_rect.top + pt_tmp.y;
+
+ return pt_result;
+}
+
+/*--------------------------------------------------------------*/
+_pointL preview_window::inches2clientpix ( _pointD & pt )
+{
+ _pointD pt_tmp;
+ _pointL pt_result;
+ double da, db;
+
+ pt_tmp.x = pt.x - m_img_rect.left;
+ pt_tmp.y = pt.y - m_img_rect.top;
+
+ da = m_img_rect.right - m_img_rect.left;
+ db = m_client_rect.right - m_client_rect.left;
+ pt_result.x = ( long ) similarity ( pt_tmp.x, da, db );
+
+ da = m_img_rect.bottom - m_img_rect.top;
+ db = m_client_rect.bottom - m_client_rect.top;
+ pt_result.y = ( long ) similarity ( pt_tmp.y, da, db );
+
+ return pt_result;
+}
+
+/*--------------------------------------------------------------*/
+int preview_window::get_marquee_point ( long i, _pointL * pt_lt, _pointL * pt_rb )
+{
+ marquee * marq;
+ _pointD pt_tmp_lt, pt_tmp_rb;
+
+ if (0 == (marq = & g_view_manager->get_marquee (i)))
+ return 0;
+
+ pt_tmp_lt = marq->offset;
+ pt_tmp_rb = marq->offset + marq->area;
+
+ * pt_lt = inches2clientpix ( pt_tmp_lt );
+ * pt_rb = inches2clientpix ( pt_tmp_rb );
+
+ return 1;
+}
+
+/*--------------------------------------------------------------*/
+int preview_window::check_min_size ( const _pointL & pt_lt, const _pointL & pt_rb )
+{
+ _pointD pt_zero_D, pt_min_D;
+ _pointL pt_min_L;
+
+ pt_zero_D.x = 0.0;
+ pt_zero_D.y = 0.0;
+ pt_min_D.x = g_min_marq_size;
+ pt_min_D.y = g_min_marq_size;
+ pt_min_L = inches2clientpix ( pt_min_D ) - inches2clientpix ( pt_zero_D );
+
+ if ( ::abs ( pt_lt.x - pt_rb.x ) < pt_min_L.x ||
+ ::abs ( pt_lt.y - pt_rb.y ) < pt_min_L.y )
+ return 0;
+ else
+ return 1;
+}
+
+/*--------------------------------------------------------------*/
+void preview_window::check_max_size ( _pointD * pt_offset, _pointD * pt_area,
+ int offset )
+{
+ if ( pt_offset->x < 0.0 ) pt_offset->x = 0.0;
+ if ( pt_offset->y < 0.0 ) pt_offset->y = 0.0;
+
+ if ( pt_area->x > m_max_img_rect.right ) pt_area->x = m_max_img_rect.right;
+ if ( pt_area->y > m_max_img_rect.bottom ) pt_area->y = m_max_img_rect.bottom;
+
+ if ( offset )
+ {
+ if ( m_max_img_rect.right < pt_offset->x + pt_area->x )
+ pt_area->x = m_max_img_rect.right - pt_offset->x;
+ if ( m_max_img_rect.bottom < pt_offset->y + pt_area->y )
+ pt_area->y = m_max_img_rect.bottom - pt_offset->y;
+ }
+ else
+ {
+ if ( m_max_img_rect.right < pt_offset->x + pt_area->x )
+ pt_offset->x = m_max_img_rect.right - pt_area->x;
+ if ( m_max_img_rect.bottom < pt_offset->y + pt_area->y )
+ pt_offset->y = m_max_img_rect.bottom - pt_area->y;
+ }
+}
+
+/*--------------------------------------------------------------*/
+void preview_window::check_ltrb ( _pointL * pt_lt, _pointL * pt_rb )
+{
+ _pointL pt_ret_lt, pt_ret_rb;
+
+ pt_ret_lt.x = ( pt_lt->x < pt_rb->x ) ? pt_lt->x : pt_rb->x;
+ pt_ret_lt.y = ( pt_lt->y < pt_rb->y ) ? pt_lt->y : pt_rb->y;
+ pt_ret_rb.x = ( pt_lt->x > pt_rb->x ) ? pt_lt->x : pt_rb->x;
+ pt_ret_rb.y = ( pt_lt->y > pt_rb->y ) ? pt_lt->y : pt_rb->y;
+
+ * pt_lt = pt_ret_lt;
+ * pt_rb = pt_ret_rb;
+}
+
+/*--------------------------------------------------------------*/
+void preview_window::draw_rect ( const _pointL & pt_lt,
+ const _pointL & pt_rb )
+{
+ int x, y, w, h;
+
+ if ( m_gc == 0 )
+ {
+ m_gc = ::gdk_gc_new ( m_prev->window );
+ ::gdk_gc_set_function ( m_gc, GDK_INVERT );
+ ::gdk_gc_set_line_attributes ( m_gc, 1, GDK_LINE_ON_OFF_DASH,
+ GDK_CAP_BUTT, GDK_JOIN_MITER );
+ }
+
+ x = ( pt_lt.x < pt_rb.x ) ? pt_lt.x : pt_rb.x;
+ y = ( pt_lt.y < pt_rb.y ) ? pt_lt.y : pt_rb.y;
+ w = ( pt_lt.x > pt_rb.x ) ? pt_lt.x - pt_rb.x : pt_rb.x - pt_lt.x;
+ h = ( pt_lt.y > pt_rb.y ) ? pt_lt.y - pt_rb.y : pt_rb.y - pt_lt.y;
+
+ ::gdk_draw_rectangle ( m_prev->window, m_gc, FALSE,
+ x, y, w, h );
+}
+
+
diff --git a/frontend/pisa_preview_window.h b/frontend/pisa_preview_window.h
new file mode 100644
index 0000000..a8aaba7
--- /dev/null
+++ b/frontend/pisa_preview_window.h
@@ -0,0 +1,171 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001, 2005, 2008 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_PREVIEW_WINDOW_H
+#define ___PISA_PREVIEW_WINDOW_H
+
+#include <gtk/gtk.h>
+#include "pisa_enums.h"
+#include "pisa_structs.h"
+
+class preview_window
+{
+ public:
+
+ preview_window ( ) { m_gc = NULL; }
+
+ // operation
+ int init ( void );
+ GtkWidget * create_window ( GtkWidget * parent );
+ int close_window ( int destroy );
+
+ int is_prev_img ( void ) { return m_is_prev; }
+
+ int resize_window ( void );
+
+ int auto_exposure ( void );
+
+ int update_img ( bool left = false );
+
+ void start_preview (void);
+ void start_zoom (void);
+
+ gint expose_event ( GtkWidget * widget, GdkEventExpose * event );
+ gint event ( GtkWidget * widget, GdkEvent * event );
+
+ void size_allocate ( GtkWidget * widget );
+
+ private:
+
+ // operation
+ GtkWidget * create_darea ( GtkWidget * parent );
+
+ long get_preview_resolution ( const _rectD * img_rect = 0 );
+ void resize_preview_window ( long width, long height );
+ void change_max_scan_area ( long width, long height );
+ void change_max_disp_area ( long width, long height );
+ void clear_image ( void );
+
+ void modify_max_val ( void );
+
+ void draw_marquee ( void );
+
+ // preview, zoom
+ void start_preview (bool zooming);
+ void reset_settings (bool zooming);
+ int set_preview_param (bool zooming);
+ void revert_resolution ( void );
+
+ void tool_usm (const pisa_image_info& info);
+ void zoom_boundary (_pointD& pt_offset, _pointD& pt_area,
+ const _pointD& pt_max, float rate) const;
+
+ // cursor
+ void create_cursor ( void );
+ int set_mouse_cursor ( int x, int y );
+ int search_cursor_state ( const _pointL & pt_lt,
+ const _pointL & pt_rb,
+ const _pointL & pt );
+ void change_cursor ( void );
+
+ // marquee
+ gint mouse_down ( GdkEvent * event );
+ gint mouse_move ( GdkEvent * event );
+ gint mouse_up ( GdkEvent * event );
+
+ int create_marquee ( const _pointL & pt_lt, const _pointL & pt_rb );
+ int delete_marquee ( void );
+
+ void move_marquee ( const _pointL & pt_lt, const _pointL & pt_rb );
+ void resize_marquee ( const _pointL & pt_lt, const _pointL & pt_rb );
+
+ void begin_mouse_move ( _pointL * pt_lt, _pointL * pt_rb );
+ void move_rect ( _pointL * pt_lt, _pointL * pt_rb, const _pointL & pt_move );
+
+ _pointD clientpix2inches ( _pointL & pt );
+ _pointL inches2clientpix ( _pointD & pt );
+ int get_marquee_point ( long i, _pointL * pt_lt, _pointL * pt_rb );
+
+ int check_min_size ( const _pointL & pt_lt, const _pointL & pt_rb );
+ void check_max_size ( _pointD * pt_offset, _pointD * pt_area,
+ int offset );
+ void check_ltrb ( _pointL * pt_lt, _pointL * pt_rb );
+
+ void draw_rect ( const _pointL & pt_lt, const _pointL & pt_rb );
+
+ // attribute
+ GtkWidget * m_win;
+ GtkWidget * m_prev;
+
+ long m_is_prev;
+ long m_img_width, m_img_height;
+ unsigned char * m_img;
+ unsigned char * m_img_org;
+
+ GdkGC * m_gc;
+
+ int m_cursor_state;
+ GdkCursor * m_cursor [ 11 ];
+
+ bool m_on_preview;
+ long m_allocate_width;
+ long m_allocate_height;
+
+ // for marquee
+ int m_drag;
+ _rectL m_client_rect;
+ _rectD m_max_img_rect;
+ _rectD m_img_rect;
+
+ _pointL m_pt_max_lt;
+ _pointL m_pt_max_rb;
+
+ _pointL m_pt_begin;
+ _pointL m_pt_old;
+ _pointL m_pt_old_lt;
+ _pointL m_pt_old_rb;
+ _pointD m_pt_save_offset;
+ _pointD m_pt_save_area;
+
+};
+
+inline void
+preview_window::start_preview (void)
+{
+ start_preview (false);
+}
+
+inline void
+preview_window::start_zoom (void)
+{
+ start_preview (true);
+}
+
+#endif // ___PISA_PREVIEW_WINDOW_H
diff --git a/frontend/pisa_progress_window.cc b/frontend/pisa_progress_window.cc
new file mode 100644
index 0000000..6cc5b50
--- /dev/null
+++ b/frontend/pisa_progress_window.cc
@@ -0,0 +1,263 @@
+/* pisa_progress_window.cc
+ Copyright (C) 2001, 2004 SEIKO EPSON CORPORATION
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#include <config.h>
+
+#include "pisa_progress_window.h"
+#include "gettext.h"
+#define _(msg_id) gettext (msg_id)
+
+#include <cmath>
+#include <iostream>
+
+#include "pisa_main_window.h"
+#include "pisa_view_manager.h"
+
+
+// callbacks for use by the GTK+ toolkit
+static gint _delete (GtkWidget *, progress_window *);
+static void _cancel (GtkWidget *, progress_window *);
+
+
+/*! Creates an instance as the child of a \a parent widget. The \a
+ parent widget, by default, is the root window.
+
+ Initially, the message will be set to progress_window::WARMING_UP
+ with the control button disabled. The instance will \e not be made
+ visible. Use the show() and hide() members to control visibility.
+*/
+progress_window:: progress_window (GtkWidget *parent)
+ : _msg (-1), _can (false), _last_step (0)
+{
+ // first, let's gtk_*_new all the main players
+ _box = (GtkDialog *) gtk_dialog_new ();
+ _txt = (GtkLabel *) gtk_label_new ("");
+ _bar = (GtkProgressBar *) gtk_progress_bar_new ();
+ _btn = (GtkButton *) gtk_button_new ();
+
+ _btn_cancel = gtk_label_new (_(" Cancel "));
+ _btn_finish = gtk_label_new (_(" Finish "));
+
+ if (!_box || !_txt || !_bar || !_btn
+ || !_btn_cancel || !_btn_finish) {
+ if (_btn_finish) gtk_widget_destroy (_btn_finish);
+ if (_btn_cancel) gtk_widget_destroy (_btn_cancel);
+
+ if (_btn) gtk_widget_destroy (GTK_WIDGET (_btn));
+ if (_bar) gtk_widget_destroy (GTK_WIDGET (_bar));
+ if (_txt) gtk_widget_destroy (GTK_WIDGET (_txt));
+ if (_box) gtk_widget_destroy (GTK_WIDGET (_box));
+ throw std::bad_alloc ();
+ }
+
+ // container
+ gtk_window_set_policy (GTK_WINDOW (_box), false, false, true);
+ gtk_window_set_title (GTK_WINDOW (_box), PACKAGE);
+ gtk_container_border_width (GTK_CONTAINER (_box->vbox), 5);
+ gtk_widget_realize (GTK_WIDGET (_box));
+ gtk_signal_connect (GTK_OBJECT (_box), "delete_event",
+ GTK_SIGNAL_FUNC (_delete), this);
+
+ // message
+ gtk_box_pack_start (GTK_BOX (_box->vbox), GTK_WIDGET(_txt),
+ false, false, 5);
+
+ // progress
+ gtk_widget_set_usize (GTK_WIDGET (_bar), 350, 20);
+ gtk_box_pack_start (GTK_BOX (_box->vbox), GTK_WIDGET(_bar),
+ false, false, 5);
+
+ // actions
+ GTK_WIDGET_SET_FLAGS (GTK_WIDGET (_btn), GTK_CAN_DEFAULT);
+ gtk_box_pack_start (GTK_BOX (_box->action_area), GTK_WIDGET (_btn),
+ false, false, 0);
+ gtk_signal_connect (GTK_OBJECT (_btn), "clicked",
+ GTK_SIGNAL_FUNC (_cancel), this);
+ gtk_widget_grab_default (GTK_WIDGET (_btn));
+
+ set_text (WARMING_UP);
+
+ gtk_window_set_modal (GTK_WINDOW (_box), true);
+ gtk_window_set_transient_for (GTK_WINDOW (_box), GTK_WINDOW (parent));
+}
+
+progress_window::~progress_window (void)
+{
+ gtk_widget_destroy (GTK_WIDGET (_btn_cancel));
+ gtk_widget_destroy (GTK_WIDGET (_btn_finish));
+
+ gtk_widget_destroy (GTK_WIDGET (_box));
+}
+
+void
+progress_window::show (void)
+{
+ gtk_widget_show_all (GTK_WIDGET (_box));
+}
+
+void
+progress_window::hide (void)
+{
+ gtk_widget_hide (GTK_WIDGET (_box));
+}
+
+/*! This member function sets the progress indicator to show the
+ percentage of \a progress that has been made in terms of an \a
+ estimated_total.
+
+ In many situations the estimated total can be known beforehand and
+ will not vary, but in some circumstances this estimate may change,
+ or become more precisely known, so that you may want to update the
+ progress indicator even if there was \e no \a progress.
+*/
+void
+progress_window::set_progress (double progress, double estimated_total)
+{
+ if (0 == estimated_total) return;
+
+ int step = _step_count * (progress / estimated_total);
+
+ if (_last_step != step)
+ {
+ gtk_progress_bar_set_fraction (_bar, progress / estimated_total);
+ _last_step = step;
+ }
+}
+
+/*! Changes the text in the message area to an arbitrary \a message.
+ */
+void
+progress_window::set_text (const char *message)
+{
+ gtk_label_set_text (_txt, message);
+}
+
+/*! Changes the text in the message area to one of the "canned"
+ messages and adjusts the control button's look and behaviour as
+ specified in the progress_window::message documentation.
+ */
+void
+progress_window::set_text (message id)
+{
+ _last_step = 0;
+ if (id == _msg) // nothing to do
+ return;
+
+ switch (id) {
+ case WARMING_UP:
+ set_text (_("Scanner is warming up. Please wait..."));
+ break;
+ case PREVIEWING:
+ set_text (_("Pre-scanning in Progress"));
+ break;
+ case SCANNING:
+ set_text (_("Scanning in Progress"));
+ break;
+ case WAITING:
+ set_text (_("Starting a sequence of scans.\n"
+ "Press to scanner's Start button to start each scan."));
+ break;
+ default: // should have been caught by the compiler!
+ throw;
+ }
+
+ if (-1 == _msg) { // we were called by the constructor
+ GtkWidget *lbl = (WAITING == id) ? _btn_finish : _btn_cancel;
+ gtk_container_add (GTK_CONTAINER (_btn), lbl);
+ }
+
+ if (WAITING == id && WAITING != _msg) {
+ flip_label (_btn_cancel, _btn_finish);
+ }
+ if (WAITING != id && WAITING == _msg) {
+ flip_label (_btn_finish, _btn_cancel);
+ }
+
+ gtk_widget_set_sensitive (GTK_WIDGET (_btn), (WARMING_UP != id));
+
+ _msg = id;
+}
+
+/*! Returns true when the user has requested cancellation of the
+ action(s) the object is reporting progress on, false otherwise.
+ */
+bool
+progress_window::is_cancelled (void) const
+{
+ return _can;
+}
+
+/*! Mainly intended for internal use (by the GTK+ callbacks, to be
+ precise), this member function raises a flag indicating that the
+ user requested cancellation of the action(s) that this object is
+ reporting on.
+
+ Cancellation requests can not be revoked.
+
+ \note It is up to the application to check for and honour such
+ requests.
+ */
+void
+progress_window::cancel (void)
+{
+ if (WARMING_UP != _msg)
+ _can = true;
+}
+
+/*! Changes the control button's label \a from one \a to another.
+
+ \internal Just removing the label from its container would result in
+ its destruction unless someone holds onto a reference for the label
+ object. Since we may want to reuse the label, we add a reference
+ before its removal.
+ */
+void
+progress_window::flip_label (GtkWidget *from, GtkWidget *to)
+{
+ gtk_object_ref (GTK_OBJECT (from));
+ gtk_container_remove (GTK_CONTAINER (_btn), from);
+ gtk_container_add (GTK_CONTAINER (_btn), to);
+ gtk_widget_show (to);
+}
+
+
+static
+gint
+_delete (GtkWidget *, progress_window *p)
+{
+ if (p)
+ p->cancel ();
+
+ return true;
+}
+
+static
+void
+_cancel (GtkWidget *, progress_window *p)
+{
+ if (p)
+ p->cancel ();
+}
diff --git a/frontend/pisa_progress_window.h b/frontend/pisa_progress_window.h
new file mode 100644
index 0000000..87ec010
--- /dev/null
+++ b/frontend/pisa_progress_window.h
@@ -0,0 +1,118 @@
+/* pisa_progress_window.h -*- C++ -*-
+ Copyright (C) 2001, 2004 SEIKO EPSON CORPORATION
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_PROGRESS_WINDOW_H
+#define ___PISA_PROGRESS_WINDOW_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+
+//! A progress feedback dialog
+/*! A progress_window provides the user visual feedback about what is
+ going on. It has space for a short to medium length message at the
+ top, a progress indicator in the middle and a control button at the
+ the bottom of the window.
+
+ The message may be changed to anything at any convenient time, but
+ there are also several "canned" messages available. These messages
+ carry some additional logic with them as well.
+
+ The control button can be used to flag cancellation of the action(s)
+ that the dialog is reporting on. However, it is the responsibility
+ of the application to check for and honour such requests.
+ */
+class progress_window
+{
+public:
+ progress_window (GtkWidget *parent = 0);
+ ~progress_window (void);
+
+ //! Makes the progress window visible.
+ void show (void);
+ //! Makes the progress window invisible.
+ void hide (void);
+
+ //! Updates the progress indicator.
+ void set_progress (double progress, double estimated_total);
+
+ //! IDs for "canned" message texts.
+ enum message {
+ /*! This message can be shown whenever it takes a while before the
+ device is ready to carry out an action. Setting this message
+ will disable the control button because things like warming up
+ typically should not be interrupted.
+ */
+ WARMING_UP,
+ /*! This message can be shown during a preview scan. The control
+ button is enabled during a preview.
+ */
+ PREVIEWING,
+ /*! This message can be shown during a normal scan. Just as with
+ a preview scan, the control button is enabled.
+ */
+ SCANNING,
+ /*! This message can be shown when the application is waiting for
+ the user to press a scanner button. The control button will be
+ enabled. Note that it may labelled differently from the other
+ cases.
+ */
+ WAITING
+ };
+
+ //! Updates the message indicating the stage of progress.
+ void set_text (const char *message);
+ //! Sets a "canned" message indicating the stage of progress.
+ void set_text (message id);
+
+ //! Indicates whether the user requested cancellation.
+ bool is_cancelled (void) const;
+
+ //! Flags a cancellation request.
+ void cancel (void);
+
+private:
+ //! Changes the text on the control button.
+ void flip_label (GtkWidget *from, GtkWidget *to);
+
+private:
+ GtkDialog *_box;
+ GtkLabel *_txt; //!< message area
+ GtkProgressBar *_bar; //!< progress indicator
+ GtkButton *_btn; //!< control button
+ GtkWidget *_btn_cancel;
+ GtkWidget *_btn_finish;
+
+ int _msg;
+ bool _can;
+
+ static const int _step_count = 50;
+ int _last_step;
+};
+
+#endif // ___PISA_PROGRESS_WINDOW_H
diff --git a/frontend/pisa_sane_scan.cc b/frontend/pisa_sane_scan.cc
new file mode 100644
index 0000000..7a60ab3
--- /dev/null
+++ b/frontend/pisa_sane_scan.cc
@@ -0,0 +1,1214 @@
+/* pisa_sane_scan.cc
+ Copyright (C) 2001, 2004, 2005, 2008, 2009 SEIKO EPSON CORPORATION
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <string>
+
+#include "pisa_sane_scan.h"
+#include "pisa_error.h"
+#include "pisa_change_unit.h"
+#include "pisa_enums.h"
+
+
+#define MM_PER_INCH 25.4
+
+#define SCSI_STR "SCSI"
+#define PIO_STR "PIO"
+#define USB_STR "USB"
+
+
+bool
+sane_scan::has_flatbed (void) const
+{
+ return (PISA_OP_FLATBED & support_option);
+}
+
+void
+sane_scan::select_flatbed (void)
+{
+ if (!has_flatbed ()) return;
+
+ _source = PISA_OP_FLATBED;
+ _film = PISA_FT_REFLECT;
+}
+
+bool
+sane_scan::using_flatbed (void) const
+{
+ return (PISA_OP_FLATBED == _source);
+}
+
+bool
+sane_scan::has_adf (void) const
+{
+ return ((PISA_OP_ADF | PISA_OP_ADFDPLX) & support_option);
+}
+
+void
+sane_scan::select_adf (bool duplex)
+{
+ if (!has_duplex ()) duplex = false;
+ if (!has_adf ()) return;
+
+ _source = (duplex ? PISA_OP_ADFDPLX : PISA_OP_ADF);
+ _film = PISA_FT_REFLECT;
+}
+
+bool
+sane_scan::using_adf (void) const
+{
+ return (PISA_OP_ADF == _source || PISA_OP_ADFDPLX == _source);
+}
+
+bool
+sane_scan::has_duplex (void) const
+{
+ return (PISA_OP_ADFDPLX & support_option);
+}
+
+bool
+sane_scan::using_duplex (void) const
+{
+ return (PISA_OP_ADFDPLX == _source);
+}
+
+bool
+sane_scan::has_tpu (void) const
+{
+ return (PISA_OP_TPU & support_option);
+}
+
+void
+sane_scan::select_tpu (bool positive)
+{
+ if (!has_tpu ()) return;
+
+ _source = PISA_OP_TPU;
+ _film = (positive ? PISA_FT_POSI : PISA_FT_NEGA);
+}
+
+bool
+sane_scan::using_tpu (void) const
+{
+ return (PISA_OP_TPU == _source);
+}
+
+bool
+sane_scan::has_dfd (void) const
+{
+ const SANE_Option_Descriptor *sod = NULL;
+ int id = 0;
+
+ try {
+ id = get_option_id ("double-feed-detection-sensitivity");
+ sod = sane_get_option_descriptor (m_hdevice, id);
+ return (sod && !(SANE_CAP_INACTIVE & sod->cap));
+ }
+ catch (pisa_error& e) {
+ if (pisa_error (PISA_ERR_UNSUPPORT) == e) return false;
+ throw e;
+ }
+}
+
+void
+sane_scan::set_dfd (long idx)
+{
+ const SANE_Option_Descriptor *sod = NULL;
+ int id = get_option_id ("double-feed-detection-sensitivity");
+
+ sod = sane_get_option_descriptor (m_hdevice, id);
+ if (!sod
+ || SANE_CAP_INACTIVE & sod->cap
+ || SANE_TYPE_STRING != sod->type
+ || SANE_CONSTRAINT_STRING_LIST != sod->constraint_type)
+ throw pisa_error (PISA_STATUS_UNSUPPORTED);
+
+ const SANE_String_Const *p = sod->constraint.string_list;
+ while (p && *p && ++p)
+ ;
+
+ if (idx < p - sod->constraint.string_list)
+ set_value ("double-feed-detection-sensitivity",
+ sod->constraint.string_list[idx]);
+ else
+ throw pisa_error (PISA_STATUS_UNSUPPORTED);
+}
+
+char
+sane_scan::get_scan_source (void) const
+{
+ if (PISA_OP_NONE == _source)
+ throw pisa_error (PISA_ERR_CONNECT);
+
+ return _source;
+}
+
+char
+sane_scan::get_film_type (void) const
+{
+ return _film;
+}
+
+void
+sane_scan::clear_button_status (void)
+{
+ // We (re)set the scan source. This indirectly resets the scanner's
+ // push button status. I'd like to do something less kludgy but for
+ // the moment can't think of anything better :-(
+ set_scan_source ();
+}
+
+void
+sane_scan::init (void)
+{
+ m_hdevice = 0;
+
+ name = 0;
+ max_resolution = -1;
+
+ _source = PISA_OP_NONE;
+ _film = PISA_FT_REFLECT;
+
+ atexit (sane_exit);
+ sane_init (0, 0);
+}
+
+void
+sane_scan::open_device (char *name)
+{
+ SANE_Status status;
+ char *device_name = 0;
+
+ if (!name)
+ {
+ device_name = new char[64];
+ query_device (device_name);
+ }
+ else
+ device_name = name;
+
+ SANE_Handle device;
+ status = sane_open (device_name, &device);
+
+ if (SANE_STATUS_GOOD != status)
+ {
+ if (device_name != name)
+ delete[] device_name;
+ throw pisa_error (status);
+ }
+
+ // we successfully opened a new device, now close whatever we are
+ // hanging onto and rebind to the newly opened device
+
+ close_device ();
+ m_hdevice = device;
+
+ get_scanner_info (device_name);
+
+ if (device_name != name)
+ delete[] device_name;
+}
+
+void
+sane_scan::close_device (void)
+{
+ if (m_hdevice)
+ sane_close (m_hdevice);
+
+ m_hdevice = 0;
+}
+
+void
+sane_scan::set_scan_source (void)
+{
+ if (!m_hdevice)
+ throw pisa_error (PISA_ERR_CONNECT);
+
+ char source[32];
+
+ if (using_flatbed ())
+ strcpy (source, "Flatbed");
+ else if (using_adf ())
+ strcpy (source, "Automatic Document Feeder");
+ else if (using_tpu ())
+ strcpy (source, "Transparency Unit");
+ else
+ throw pisa_error (PISA_ERR_PARAMETER);
+
+ set_value (SANE_NAME_SCAN_SOURCE, source);
+
+ if (using_adf ())
+ {
+ char mode[16];
+
+ strcpy (mode, (using_duplex () ? "Duplex" : "Simplex"));
+ set_value ("adf-mode", mode);
+ }
+
+ max_resolution = -1;
+}
+
+void
+sane_scan::set_film_type (void)
+{
+ char film_type[32];
+
+ switch (_film)
+ {
+ case PISA_FT_POSI:
+ strcpy (film_type, "Positive Film");
+ break;
+
+ case PISA_FT_NEGA:
+ strcpy (film_type, "Negative Film");
+ break;
+
+ case PISA_FT_REFLECT:
+ return;
+ }
+
+ set_value ("film-type", (void *) film_type);
+}
+
+void
+sane_scan::get_current_max_size (double *width, double *height)
+{
+ SANE_Int max_x = 0;
+ SANE_Int max_y = 0;
+
+ if (!m_hdevice)
+ throw pisa_error ( PISA_ERR_CONNECT);
+
+ set_scan_source ();
+ set_film_type ();
+
+ int opt_id = 0;
+ const SANE_Option_Descriptor *opt_desc = NULL;
+
+ opt_id = get_option_id (SANE_NAME_SCAN_BR_X);
+ opt_desc = sane_get_option_descriptor (m_hdevice, opt_id);
+ if (!opt_desc || SANE_CONSTRAINT_RANGE != opt_desc->constraint_type)
+ throw pisa_error (PISA_STATUS_UNSUPPORTED);
+
+ max_x = opt_desc->constraint.range->max;
+
+ opt_id = get_option_id (SANE_NAME_SCAN_BR_Y);
+ opt_desc = sane_get_option_descriptor (m_hdevice, opt_id);
+ if (!opt_desc || SANE_CONSTRAINT_RANGE != opt_desc->constraint_type)
+ throw pisa_error (PISA_STATUS_UNSUPPORTED);
+
+ max_y = opt_desc->constraint.range->max;
+
+ *width = SANE_UNFIX (max_x) / MM_PER_INCH;
+ *height = SANE_UNFIX (max_y) / MM_PER_INCH;
+}
+
+
+void
+sane_scan::get_color_profile (double *coef) const
+{
+ SANE_Word value;
+ char option_name[16];
+ int i;
+
+ if (!m_hdevice)
+ throw pisa_error (PISA_ERR_CONNECT);
+
+ for (i = 0; i < 9; i++)
+ {
+ sprintf (option_name, "cct-%d", i + 1);
+ get_value (option_name, (void *) &value);
+ coef[i] = SANE_UNFIX (value);
+ }
+}
+
+void
+sane_scan::start_scan (int * width, int * height)
+{
+ SANE_Status status;
+
+ if (! m_hdevice)
+ throw pisa_error (PISA_ERR_CONNECT);
+
+ if (SANE_STATUS_GOOD != (status = sane_start (m_hdevice)))
+ {
+ pisa_error oops (status, *this);
+ if (!using_adf ()
+ || SANE_STATUS_NO_DOCS != status) sane_cancel (m_hdevice);
+ throw oops;
+ }
+
+ if (SANE_STATUS_GOOD != (status = sane_get_parameters (m_hdevice,
+ &m_sane_para)))
+ {
+ pisa_error oops (status, *this);
+ sane_cancel (m_hdevice);
+ throw oops;
+ }
+
+ *width = m_sane_para.pixels_per_line;
+ *height = m_sane_para.lines;
+
+ m_rows = 0;
+}
+
+SANE_Status
+sane_scan::acquire_image (unsigned char *img, int row_bytes,
+ int height, int cancel)
+{
+ if (!m_hdevice)
+ throw pisa_error (PISA_ERR_CONNECT);
+
+ SANE_Status status = SANE_STATUS_GOOD;
+ unsigned char *cur_pos = img;
+
+ for (int i = 0; i < height; i++, cur_pos += row_bytes)
+ {
+ m_rows++;
+ if (cancel)
+ {
+ sane_cancel (m_hdevice);
+ return SANE_STATUS_CANCELLED;
+ }
+
+ // The SANE standard does not promise to return as much data as
+ // we request, so we keep asking until we got all that we want.
+
+ int cnt = row_bytes;
+ int len = 0;
+ while (SANE_STATUS_GOOD == status && cnt > 0)
+ {
+ status = sane_read (m_hdevice, cur_pos + (row_bytes - cnt),
+ cnt, &len);
+
+ cnt -= len;
+ }
+
+ if (status == SANE_STATUS_EOF)
+ break;
+
+ if (status != SANE_STATUS_GOOD)
+ {
+ throw pisa_error (status, *this);
+ }
+
+ }
+
+ return status;
+}
+
+/*! \brief Returns the largest resolution not larger than a \a cutoff.
+
+ Returns the largest supported hardware resolution that does not
+ exceed the \a cutoff. Passing a negative value for the \a cutoff,
+ which is the default, returns the maximum resolution supported by
+ the hardware.
+
+ Throws an unsupported exception when no suitable resolution can be
+ determined.
+ */
+long
+sane_scan::get_max_resolution (long cutoff) const
+{
+ if (0 > cutoff // use cached value if possible
+ && 0 < max_resolution)
+ return max_resolution;
+
+ long result = -1;
+
+ try
+ {
+ long r;
+
+ r = get_max_resolution (SANE_NAME_SCAN_X_RESOLUTION, cutoff);
+ if (r > result) result = r;
+ r = get_max_resolution (SANE_NAME_SCAN_Y_RESOLUTION, cutoff);
+ if (r > result) result = r;
+ }
+ catch (const pisa_error& e)
+ {
+ pisa_error expected (PISA_STATUS_UNSUPPORTED);
+
+ if (e != expected) throw;
+ }
+
+ if (0 > result)
+ throw pisa_error (PISA_STATUS_UNSUPPORTED);
+
+ if (0 > cutoff // update cached value if necessary
+ && 0 > max_resolution)
+ const_cast <sane_scan *> (this)->max_resolution = result;
+
+ return result;
+}
+
+long
+sane_scan::get_max_resolution (SANE_String_Const option_name,
+ long cutoff) const
+{
+ int opt_id = get_option_id (option_name);
+
+ const SANE_Option_Descriptor
+ *opt_desc = sane_get_option_descriptor (m_hdevice, opt_id);
+
+ if (!opt_desc || SANE_TYPE_INT != opt_desc->type)
+ throw pisa_error (PISA_STATUS_UNSUPPORTED);
+
+ long result = -1;
+
+ switch (opt_desc->constraint_type)
+ {
+ case SANE_CONSTRAINT_RANGE:
+ {
+ const SANE_Range *const range = opt_desc->constraint.range;
+
+ if (0 > cutoff) cutoff = range->max;
+ if (0 == range->quant)
+ {
+ result = cutoff;
+ }
+ else
+ { // relies on integer arithmetic
+ result = (((cutoff - range->min) / range->quant)
+ * range->quant) + range->min;
+ }
+ break;
+ }
+ case SANE_CONSTRAINT_WORD_LIST:
+ { // assumes list is in ascending order
+ const SANE_Word *list = opt_desc->constraint.word_list;
+
+ size_t last = list[0];
+
+ if (0 > cutoff) cutoff = list[last];
+ while (0 < last && cutoff < list[last])
+ {
+ --last;
+ }
+ if (0 < last) result = list[last];
+ break;
+ }
+ default:
+ throw pisa_error (PISA_STATUS_UNSUPPORTED);
+ }
+
+ if (0 > result)
+ throw pisa_error (PISA_STATUS_UNSUPPORTED);
+
+ return result;
+}
+
+long
+sane_scan::get_resolution (SANE_String_Const scan_direction,
+ int min_res) const
+{
+ SANE_Int def_res_tbl[] = { 75, 150, 300, 600, 1200 };
+ SANE_Int def_res_cnt = 4;
+
+ const SANE_Int *res_tbl;
+ SANE_Int res_cnt;
+
+ { // initialize resolution table
+ int opt_id = get_option_id (scan_direction);
+ const SANE_Option_Descriptor *opt_desc
+ = sane_get_option_descriptor (m_hdevice, opt_id);
+
+ if (opt_desc && SANE_CONSTRAINT_RANGE == opt_desc->constraint_type)
+ {
+ long res = min_res;
+ long min = opt_desc->constraint.range->min;
+ long max = opt_desc->constraint.range->max;
+
+ if (res < min) res = min;
+ if (res > max) res = max;
+
+ return res;
+ }
+ else if (opt_desc
+ && SANE_TYPE_INT == opt_desc->type
+ && SANE_CONSTRAINT_WORD_LIST == opt_desc->constraint_type)
+ {
+ res_cnt = opt_desc->constraint.word_list[0];
+ res_tbl = &(opt_desc->constraint.word_list[1]);
+ }
+ else
+ {
+ res_cnt = def_res_cnt;
+ res_tbl = def_res_tbl;
+
+ if (1200 == get_max_resolution ())
+ ++res_cnt;
+ }
+ }
+
+ { // find appropriate resolution
+ int i = 0;
+ while (i < res_cnt && res_tbl[i] < min_res)
+ {
+ ++i;
+ }
+ return (res_cnt == i ? 0 : res_tbl[i]);
+ }
+}
+
+void
+sane_scan::get_scanner_info (const char *dev_name)
+{
+ int i, opt_id;
+ const SANE_Option_Descriptor *opt_desc;
+ const char *fbf = "Flatbed";
+ const char *tpu = "Transparency Unit";
+ const char *adf = "Automatic Document Feeder";
+
+ if (dev_name)
+ {
+ char *copy = strdup (dev_name);
+ if (!copy)
+ throw pisa_error (PISA_STATUS_NO_MEM);
+
+ if (name)
+ free (name);
+ name = copy;
+ }
+ support_option = 0;
+ opt_id = get_option_id (SANE_NAME_SCAN_SOURCE);
+ opt_desc = sane_get_option_descriptor (m_hdevice, opt_id);
+
+ if (opt_desc->type != SANE_TYPE_STRING ||
+ opt_desc->constraint_type != SANE_CONSTRAINT_STRING_LIST)
+ throw pisa_error (PISA_ERR_CONNECT);
+
+ for (i = 0; opt_desc->constraint.string_list[i]; i++)
+ {
+ if (0 == strcmp (opt_desc->constraint.string_list[i], fbf))
+ {
+ support_option |= PISA_OP_FLATBED;
+ }
+ if (0 == strcmp (opt_desc->constraint.string_list[i], tpu))
+ {
+ support_option |= PISA_OP_TPU;
+ }
+ if (0 == strcmp (opt_desc->constraint.string_list[i], adf))
+ {
+ support_option |= PISA_OP_ADF;
+ }
+ }
+
+ if (PISA_OP_NONE == _source)
+ {
+ if (has_flatbed ())
+ _source = PISA_OP_FLATBED;
+ else if (has_adf ())
+ _source = PISA_OP_ADF;
+ else if (has_tpu ())
+ _source = PISA_OP_TPU;
+ else
+ throw pisa_error (PISA_ERR_CONNECT);
+ }
+
+ if (has_adf ())
+ {
+ char old = _source;
+ _source = PISA_OP_ADF;
+ set_scan_source ();
+ if (is_activate ("adf-mode"))
+ support_option |= PISA_OP_ADFDPLX;
+ _source = old;
+ }
+ set_scan_source ();
+
+ max_resolution = get_max_resolution ();
+}
+
+void
+sane_scan::query_device (char *device_name) const
+{
+ const SANE_Device ** ppdevice_list;
+ SANE_Status status;
+ int i;
+
+ status = sane_get_devices (& ppdevice_list, SANE_TRUE);
+
+ if (status != SANE_STATUS_GOOD)
+ throw pisa_error (status);
+
+ if (*ppdevice_list == 0 ||
+ ppdevice_list[0]->vendor == 0)
+ throw pisa_error (PISA_ERR_CONNECT);
+
+ for (i = 0; ppdevice_list[i]; i++)
+ {
+ if (0 == strcasecmp (ppdevice_list[i]->vendor, "epson"))
+ {
+ strcpy (device_name, ppdevice_list[i]->name);
+ return;
+ }
+ }
+
+ throw pisa_error (PISA_ERR_CONNECT);
+}
+
+void
+sane_scan::finish_acquire (bool eject)
+{
+ if (eject && using_adf ())
+ {
+ set_value ("eject", NULL);
+ }
+ sane_cancel (m_hdevice);
+}
+
+int
+sane_scan::is_activate (const char *option_name) const
+{
+ const SANE_Option_Descriptor * opt_desc;
+ SANE_Int num_dev_options;
+ SANE_Status status;
+ int i;
+ int ret;
+
+ ret = 0;
+ status = sane_control_option (m_hdevice, 0,
+ SANE_ACTION_GET_VALUE,
+ &num_dev_options,
+ 0);
+
+ if (status != SANE_STATUS_GOOD)
+ throw pisa_error (status, *this);
+
+ for (i = 0; i < num_dev_options; i++)
+ {
+ opt_desc = sane_get_option_descriptor (m_hdevice, i);
+
+ if (opt_desc->name && strcmp (opt_desc->name, option_name) == 0)
+ {
+ if (SANE_OPTION_IS_ACTIVE (opt_desc->cap))
+ {
+ ret = 1;
+ break;
+ }
+ else
+ break;
+ }
+ }
+
+ return ret;
+}
+
+int
+sane_scan::get_option_id (const char *option_name) const
+{
+ const SANE_Option_Descriptor * opt_desc;
+ SANE_Int num_dev_options;
+ SANE_Status status;
+ int i;
+
+ status = sane_control_option (m_hdevice, 0,
+ SANE_ACTION_GET_VALUE,
+ &num_dev_options,
+ 0);
+
+ if (status != SANE_STATUS_GOOD)
+ throw pisa_error (status, *this);
+
+ for (i = 0; i < num_dev_options; i++)
+ {
+ opt_desc = sane_get_option_descriptor (m_hdevice, i);
+
+ if (opt_desc->name && strcmp (opt_desc->name, option_name) == 0)
+ return i; // found
+ }
+
+ throw pisa_error (PISA_ERR_UNSUPPORT);
+}
+
+void
+sane_scan::set_value (const char * option_name, const void * value)
+{
+ SANE_Status status;
+ int option_id;
+
+ option_id = get_option_id (option_name);
+
+ status = sane_control_option (m_hdevice,
+ option_id,
+ SANE_ACTION_SET_VALUE,
+ const_cast<void *>(value),
+ 0);
+
+ if (status != SANE_STATUS_GOOD)
+ throw pisa_error (status, *this);
+}
+
+void
+sane_scan::get_value (const char *option_name, void *value, bool nothrow) const
+{
+ SANE_Status status;
+ int option_id;
+
+ try
+ {
+ option_id = get_option_id (option_name);
+ }
+ catch (pisa_error& oops)
+ {
+ if (!nothrow) throw oops;
+ return;
+ }
+
+ status = sane_control_option (m_hdevice, option_id,
+ SANE_ACTION_GET_VALUE,
+ value,
+ 0);
+
+ if (!nothrow && status != SANE_STATUS_GOOD)
+ throw pisa_error (status, *this);
+}
+
+void
+sane_scan::get_range (const char *option_name,
+ float *max, float *min, float *step)
+{
+ const SANE_Option_Descriptor *opt_desc = NULL;
+ int opt_id = 0;
+
+ if (!option_name)
+ {
+ throw pisa_error (PISA_ERR_PARAMETER);
+ }
+ if (!m_hdevice)
+ {
+ throw pisa_error (PISA_ERR_CONNECT);
+ }
+ opt_id = get_option_id (option_name);
+ opt_desc = sane_get_option_descriptor (m_hdevice, opt_id);
+ if (!opt_desc
+ || SANE_CONSTRAINT_RANGE != opt_desc->constraint_type)
+ {
+ throw pisa_error (PISA_STATUS_UNSUPPORTED);
+ }
+
+ if ( SANE_TYPE_FIXED == opt_desc->type )
+ {
+ if (max)
+ {
+ *max = SANE_UNFIX (opt_desc->constraint.range->max);
+ }
+ if (min)
+ {
+ *min = SANE_UNFIX (opt_desc->constraint.range->min);
+ }
+ if (step)
+ {
+ *step = SANE_UNFIX (opt_desc->constraint.range->quant);
+ }
+ }
+ else
+ {
+ if (max)
+ {
+ *max = opt_desc->constraint.range->max;
+ }
+ if (min)
+ {
+ *min = opt_desc->constraint.range->min;
+ }
+ if (step)
+ {
+ *step = opt_desc->constraint.range->quant;
+ }
+ }
+}
+
+void
+sane_scan::set_preview (bool value)
+{
+ SANE_Bool v = value;
+ set_value ("preview", &v);
+}
+
+bool
+sane_scan::has_preview (void) const
+{
+ return is_activate ("preview");
+}
+
+void
+sane_scan::set_focus (long position)
+{
+ char focus[32];
+
+ if (0 == is_activate ("focus-position"))
+ return;
+
+ if (position == 25)
+ strcpy (focus, "Focus 2.5mm above glass");
+ else
+ strcpy (focus, "Focus on glass");
+
+ set_value ("focus-position", (void *) focus);
+}
+
+void
+sane_scan::set_speed (long speed)
+{
+ SANE_Bool value;
+
+ if (0 == is_activate ("speed"))
+ return;
+
+ if (speed == 1)
+ value = SANE_TRUE;
+ else
+ value = SANE_FALSE;
+
+ set_value ("speed", (void *) & value);
+}
+
+bool
+sane_scan::has_zoom (void) const
+{
+ return is_activate ("zoom");
+}
+
+bool
+sane_scan::has_focus (void) const
+{
+ return is_activate ("focus-position");
+
+}
+
+bool
+sane_scan::has_draft_mode (void) const
+{
+ return is_activate ("speed");
+}
+
+bool
+sane_scan::get_size_check (void) const
+{
+ SANE_Bool value;
+ get_value ("detect-doc-size", &value);
+ return value;
+}
+
+void
+sane_scan::set_size_check (bool value)
+{
+ SANE_Bool v = value;
+ set_value ("detect-doc-size", &v);
+}
+
+bool
+sane_scan::has_size_check (void) const
+{
+ return is_activate ("detect-doc-size");
+}
+
+bool
+sane_scan::get_autocrop (void) const
+{
+ SANE_Bool value;
+ get_value ("autocrop", &value);
+ return value;
+}
+
+void
+sane_scan::set_autocrop (bool value)
+{
+ SANE_Bool v = value;
+ set_value ("autocrop", &v);
+}
+
+bool
+sane_scan::has_autocrop (void) const
+{
+ return is_activate ("autocrop");
+}
+
+bool
+sane_scan::get_deskew (void) const
+{
+ SANE_Bool value;
+ get_value ("deskew", &value);
+ return value;
+}
+
+void
+sane_scan::set_deskew (bool value)
+{
+ SANE_Bool v = value;
+ set_value ("deskew", &v);
+}
+
+bool
+sane_scan::has_deskew (void) const
+{
+ return is_activate ("deskew");
+}
+
+bool
+sane_scan::has_start_button (void) const
+{
+ return is_activate ("monitor-button");
+}
+
+bool
+sane_scan::is_button_pressed (void) const
+{
+ SANE_Bool value = false;
+
+ get_value ("monitor-button", (void *) &value);
+
+ return value;
+}
+
+long
+sane_scan::get_polling_time (void) const
+{
+ SANE_Word value = 0;
+
+ get_value ("polling-time", (void *) &value);
+
+ return value;
+}
+
+void
+sane_scan::disable_wait_for_button (void)
+{
+ SANE_Bool value = false;
+ SANE_Bool off = false;
+ if (is_activate ("wait-for-button"))
+ {
+ get_value ("wait-for-button", &value);
+ }
+ if (value)
+ {
+ set_value ("wait-for-button", &off);
+ }
+}
+
+void
+sane_scan::set_color_mode (char pixeltype, char bitdepth)
+{
+ char color_mode[32];
+ SANE_Word depth;
+
+ switch (pixeltype)
+ {
+ case PISA_PT_RGB:
+ strcpy (color_mode, "Color");
+ break;
+
+ case PISA_PT_GRAY:
+ strcpy (color_mode, "Gray");
+ break;
+
+ case PISA_PT_BW:
+ strcpy (color_mode, "Binary");
+ break;
+ }
+
+ switch (bitdepth)
+ {
+ case PISA_BD_1:
+ depth = 1;
+ break;
+
+ case PISA_BD_8:
+ depth = 8;
+ break;
+ }
+
+ if (is_activate (SANE_NAME_SCAN_MODE))
+ {
+ set_value (SANE_NAME_SCAN_MODE, (void *) color_mode);
+ }
+ if (is_activate (SANE_NAME_BIT_DEPTH))
+ {
+ set_value (SANE_NAME_BIT_DEPTH, (void *) &depth);
+ }
+}
+
+void
+sane_scan::set_gamma_table (const unsigned char *gamma_table)
+{
+ SANE_Word table[256];
+ int i;
+ char user_defined[32];
+
+ strcpy (user_defined, "User defined (Gamma=1.8)");
+
+ set_value ("gamma-correction", (void *) user_defined);
+
+ // red
+ for (i = 0; i < 256; i++)
+ table[i] = gamma_table[256 * 0 + i];
+ set_value (SANE_NAME_GAMMA_VECTOR_R, (void *) table);
+
+ // green
+ for (i = 0; i < 256; i++)
+ table[i] = gamma_table[256 * 1 + i];
+ set_value (SANE_NAME_GAMMA_VECTOR_G, (void *) table);
+
+ // blue
+ for (i = 0; i < 256; i++)
+ table[i] = gamma_table[256 * 2 + i];
+ set_value (SANE_NAME_GAMMA_VECTOR_B, (void *) table );
+}
+
+void
+sane_scan::set_threshold (long threshold)
+{
+ set_value (SANE_NAME_THRESHOLD, (void *) & threshold);
+}
+
+void
+sane_scan::set_color_profile (const double * coef)
+{
+ SANE_Word value;
+ char option_name[16];
+ int i;
+ char user_defined[32];
+
+ strcpy (user_defined, "User defined");
+
+ set_value ("color-correction", (void *) user_defined);
+
+ for (i = 0; i < 9; i++)
+ {
+ sprintf (option_name, "cct-%d", i + 1);
+ value = SANE_FIX (coef[i]);
+ set_value (option_name, (void *) &value);
+ }
+}
+
+void
+sane_scan::set_brightness (long brightness)
+{
+ set_value (SANE_NAME_BRIGHTNESS, (void *) & brightness);
+}
+
+bool
+sane_scan::has_brightness (void) const
+{
+ return is_activate (SANE_NAME_BRIGHTNESS);
+}
+
+void
+sane_scan::set_contrast (long contrast)
+{
+ set_value (SANE_NAME_CONTRAST, (void *) & contrast);
+}
+
+bool
+sane_scan::has_contrast (void) const
+{
+ return is_activate (SANE_NAME_CONTRAST);
+}
+
+void
+sane_scan::set_brightness_method (br_method_val val)
+{
+ char method[32];
+
+ switch (val)
+ {
+ case br_gimp:
+ strcpy (method, "gimp");
+ break;
+ case br_iscan:
+ default:
+ strcpy (method, "iscan");
+ break;
+ }
+
+ set_value ("brightness-method", (void *) method);
+}
+
+void
+sane_scan::set_scan_resolution (int resolution_x, int resolution_y)
+{
+ set_value (SANE_NAME_SCAN_X_RESOLUTION, (void *) &resolution_x);
+ set_value (SANE_NAME_SCAN_Y_RESOLUTION, (void *) &resolution_y);
+}
+
+void
+sane_scan::set_scan_zoom (int zoom_x, int zoom_y)
+{
+ set_value ("zoom", (void *) & zoom_x);
+
+ zoom_y = zoom_y;
+}
+
+void
+sane_scan::set_scan_area (double offset_x, double offset_y,
+ double width, double height)
+{
+ SANE_Word value;
+ double tmp;
+
+ value = SANE_FIX (offset_x * MM_PER_INCH);
+ set_value (SANE_NAME_SCAN_TL_X, (void *) &value);
+
+ value = SANE_FIX (offset_y * MM_PER_INCH);
+ set_value (SANE_NAME_SCAN_TL_Y, (void *) &value);
+
+ tmp = offset_x + width;
+ value = SANE_FIX (tmp * MM_PER_INCH);
+ set_value (SANE_NAME_SCAN_BR_X, (void *) &value);
+
+ tmp = offset_y + height;
+ value = SANE_FIX (tmp * MM_PER_INCH);
+ set_value (SANE_NAME_SCAN_BR_Y, (void *) &value);
+}
+
+bool
+sane_scan::has_clean (void) const
+{
+ return is_activate ("clean");
+}
+
+bool
+sane_scan::has_calibrate (void) const
+{
+ return is_activate ("calibrate");
+}
+
+void
+sane_scan::clean (void)
+{
+ set_value ("clean", NULL);
+}
+
+void
+sane_scan::calibrate (void)
+{
+ set_value ("calibrate", NULL);
+}
diff --git a/frontend/pisa_sane_scan.h b/frontend/pisa_sane_scan.h
new file mode 100644
index 0000000..a6efdfa
--- /dev/null
+++ b/frontend/pisa_sane_scan.h
@@ -0,0 +1,172 @@
+/* pisa_sane_scan.h -*- C++ -*-
+ Copyright (C) 2001, 2004, 2008, 2009 SEIKO EPSON CORPORATION
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_SANE_SCAN_H
+#define ___PISA_SANE_SCAN_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sane/sane.h>
+#include <sane/saneopts.h>
+
+#undef SANE_NAME_SCAN_X_RESOLUTION
+#define SANE_NAME_SCAN_X_RESOLUTION "x-resolution"
+
+#ifdef __cplusplus
+}
+#endif
+
+enum br_method_val
+{
+ br_iscan,
+ br_gimp
+};
+
+class sane_scan
+{
+public:
+ bool has_flatbed (void) const;
+ void select_flatbed (void);
+ bool using_flatbed (void) const;
+
+ bool has_adf (void) const;
+ void select_adf (bool duplex);
+ bool using_adf (void) const;
+
+ bool has_duplex (void) const;
+ bool using_duplex (void) const;
+
+ bool has_tpu (void) const;
+ void select_tpu (bool positive);
+ bool using_tpu (void) const;
+
+ bool has_dfd (void) const;
+ void set_dfd (long idx);
+
+ char get_scan_source (void) const;
+ char get_film_type (void) const;
+
+ void clear_button_status (void);
+
+
+ void close_device (void);
+
+ // queries
+ void get_current_max_size (double *width, double *height);
+ long get_max_resolution (long cutoff = -1) const;
+ void set_scan_resolution (int resolution_x, int resolution_y);
+ void get_color_profile (double *coef) const;
+ bool has_zoom (void) const;
+ bool has_focus (void) const;
+ bool has_draft_mode (void) const;
+ bool get_size_check (void) const;
+ void set_size_check (bool value);
+ bool has_size_check (void) const;
+ bool get_autocrop (void) const;
+ void set_autocrop (bool value);
+ bool has_autocrop (void) const;
+ bool has_start_button (void) const;
+ bool get_deskew (void) const;
+ void set_deskew (bool value);
+ bool has_deskew (void) const;
+ void set_brightness (long brightness);
+ bool has_brightness (void) const;
+ void set_contrast (long contrast);
+ bool has_contrast (void) const;
+
+ void set_brightness_method (br_method_val val);
+ void set_color_mode (char pixeltype, char bitdepth);
+
+ bool is_button_pressed (void) const;
+ long get_polling_time (void) const;
+
+ void disable_wait_for_button (void);
+
+ void finish_acquire (bool eject = false);
+
+ void get_value (const char *option_name, void *value,
+ bool nothrow = false) const;
+
+ void get_range (const char *option_name,
+ float *max, float *min, float *step);
+
+ void set_preview (bool value);
+ bool has_preview (void) const;
+
+ bool has_clean (void) const;
+ bool has_calibrate (void) const;
+ void clean (void);
+ void calibrate (void);
+
+protected:
+
+ // mutators
+ void set_scan_source (void);
+ void set_film_type (void);
+
+ void init (void);
+ void open_device (char *name = 0);
+
+ void start_scan (int *width, int *height);
+ SANE_Status acquire_image (unsigned char *img, int row_bytes, int height,
+ int cancel);
+
+ int is_activate (const char *option_name) const;
+ long get_resolution (SANE_String_Const direction, int min_res) const;
+ long get_max_resolution (const char *option_name, long cutoff = -1) const;
+ int get_option_id (const char *option_name) const;
+
+ void set_value (const char *option_name, const void *value);
+
+ void query_device (char *device_name) const;
+ void get_scanner_info (const char *name = 0);
+
+ void set_color_profile (const double *coef);
+ void set_focus (long position);
+ void set_gamma_table (const unsigned char *gamma_table);
+ void set_scan_area (double offset_x, double offset_y,
+ double width, double height);
+ void set_scan_zoom (int zoom_x, int zoom_y);
+ void set_speed (long speed);
+ void set_threshold (long threshold);
+
+private:
+ SANE_Handle m_hdevice;
+ SANE_Parameters m_sane_para;
+ long m_rows;
+
+ char * name;
+ char support_option;
+ long max_resolution;
+
+ char _source;
+ char _film;
+};
+
+
+#endif // ___PISA_SANE_SCAN_H
diff --git a/frontend/pisa_scan_manager.cc b/frontend/pisa_scan_manager.cc
new file mode 100644
index 0000000..479fbb4
--- /dev/null
+++ b/frontend/pisa_scan_manager.cc
@@ -0,0 +1,1003 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001, 2005, 2008, 2009 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+/*------------------------------------------------------------*/
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <new>
+using std::bad_alloc;
+
+
+/*------------------------------------------------------------*/
+#include "pisa_scan_manager.h"
+#include "pisa_error.h"
+#include "pisa_scan_tool.h"
+#include "pisa_change_unit.h"
+
+/*------------------------------------------------------------*/
+#define SCALING_INPUT_WIDTH_LIMIT 21840
+
+/*------------------------------------------------------------*/
+void scan_manager::open_device ( char * name )
+{
+ sane_scan::init ( );
+
+ m_resize_cls = 0;
+ m_moire_cls = 0;
+ m_sharp_cls = 0;
+ m_resize_img_info = 0;
+ m_moire_img_info = 0;
+ m_sharp_img_info = 0;
+
+ _has_prev_img = false;
+
+ sane_scan::open_device (name);
+ _max_descreen_resolution = get_max_resolution (has_zoom () ? 800 : 600);
+}
+
+void
+scan_manager::set_scan_parameters (bool is_preview)
+{
+ for (int i = 0; i < 2; ++i)
+ {
+ adjust_scan_param (&_resolution[i], &_zoom[i]);
+ }
+ init_img_process_info ();
+ update_settings (is_preview);
+}
+
+void
+scan_manager::start_scan (int *width, int *height)
+{
+ sane_scan::start_scan (width, height);
+ modify_img_info (width, height);
+ create_img_cls ();
+}
+
+void
+scan_manager::init_preview (int *width, int *height)
+{
+ set_scan_parameters (true);
+ start_scan (width, height);
+}
+
+void
+scan_manager::init_scan (int *width, int *height, bool reset_params)
+{
+ if (reset_params) set_scan_parameters ();
+
+ if (area_is_too_large ())
+ {
+ if (getenv ("ISCAN_DEBUG"))
+ {
+ fprintf (stderr, "Backend will try to scan with these settings\n");
+ fprintf (stderr, "Resolution (main,sub): %ld,%ld\n",
+ _resolution[0], _resolution[1]);
+ fprintf (stderr, "Zoom (main,sub): %ld,%ld\n",
+ _zoom[0], _zoom[1]);
+ fprintf (stderr, "Offset (main,sub): %ld,%ld\n",
+ inch2pixel (_offset[0], _resolution[0], _zoom[0]),
+ inch2pixel (_offset[1], _resolution[1], _zoom[1]));
+ fprintf (stderr, "Area (main,sub): %ld,%ld\n",
+ inch2pixel (true , _area[0], _resolution[0], _zoom[0]),
+ inch2pixel (false, _area[1], _resolution[1], _zoom[1]));
+ }
+
+ throw pisa_error (PISA_ERR_AREALARGE);
+ }
+
+ start_scan (width, height);
+}
+
+/*------------------------------------------------------------*/
+void scan_manager::acquire_image ( unsigned char * img,
+ int row_bytes,
+ int height,
+ int cancel )
+{
+ if ( m_resize || m_moire || m_sharp )
+ {
+ int in_rowbytes = 0, in_line;
+ unsigned char * in_img = 0;
+
+ if ( m_sharp )
+ in_rowbytes = m_sharp_info.in_rowbytes;
+ if ( m_moire )
+ in_rowbytes = m_moire_info.in_rowbytes;
+ if ( m_resize )
+ in_rowbytes = m_resize_info.in_rowbytes;
+
+ if ( cancel || img == 0 )
+ {
+ if (0 < row_bytes)
+ {
+ // Run kludge to trigger a SANE_STATUS_EOF return value
+ // on sane_read() in sane_scan::acquire_image(), as per
+ // SANE specification requirements.
+ // Fixes problems when used with `net` backend.
+
+ unsigned char b[512];
+ SANE_Status s = SANE_STATUS_GOOD;
+ while (SANE_STATUS_GOOD == s)
+ {
+ s = sane_scan::acquire_image (b, sizeof (b), 1, cancel);
+ }
+ return;
+ }
+ else
+ {
+ sane_scan::acquire_image (0, 0, height, cancel);
+ }
+ }
+ else
+ {
+ in_line = get_send_in_line ( 1 );
+
+ if ( 0 < in_line )
+ {
+ int buf_size = in_line * in_rowbytes;
+
+ in_img = new unsigned char [ buf_size + in_rowbytes ];
+
+ if ( in_img == 0 )
+ {
+ sane_scan::acquire_image ( 0, 0, 1, 1 );
+
+ throw pisa_error ( PISA_ERR_OUTOFMEMORY );
+ }
+
+ sane_scan::acquire_image ( in_img,
+ in_rowbytes,
+ in_line,
+ cancel );
+ }
+ }
+
+ ::memset ( img, 128, row_bytes );
+ if ( cancel == 0 && PISA_ERR_SUCCESS != image_process ( in_img, img ) )
+ {
+ delete [ ] in_img;
+ sane_scan::acquire_image ( 0, 0, 1, 1 );
+ throw pisa_error ( PISA_ERR_OUTOFMEMORY );
+ }
+
+ if ( in_img )
+ delete [ ] in_img;
+ }
+ else
+ sane_scan::acquire_image ( img, row_bytes, height, cancel );
+}
+
+int
+scan_manager::init_img_process_info ()
+{
+ // set flag
+ if (!has_zoom ())
+ m_resize = 1;
+ else
+ m_resize = 0;
+
+ if (_de_screening && !get_size_check()) // color or B/W document
+ {
+ m_moire = 1;
+ m_resize = 0;
+ }
+ else
+ m_moire = 0;
+
+ m_sharp = _usm;
+
+ if ( m_resize )
+ init_zoom (&m_resize_info);
+
+ if ( m_moire )
+ init_moire (&m_moire_info);
+
+ if ( m_sharp )
+ init_sharp (&m_sharp_info);
+
+ return PISA_ERR_PARAMETER;
+}
+
+int
+scan_manager::init_zoom (resize_img_info *info)
+{
+ long act_res[2];
+
+ info->resolution = _resolution[0];
+
+ for (int i = 0; i < 2; ++i)
+ {
+ act_res[i] = _resolution[i] * _zoom[i] / 100;
+ }
+
+ info->out_width = ::inch2width (_area[0], act_res[0]);
+ info->out_height = ::inch2height (_area[1], act_res[1]);
+ info->out_rowbytes = calc_rowbytes (info->out_width,
+ static_cast <pisa_pixel_type>
+ (_pixeltype));
+
+ for (int i = 0; i < 2; ++i)
+ {
+ _resolution[i] = act_res[i];
+ _zoom[i] = 100;
+ }
+
+ try
+ {
+ get_valid_resolution (&_resolution[0], &_resolution[1], true);
+ }
+ catch (const pisa_error& e)
+ {
+ if (pisa_error (PISA_STATUS_UNSUPPORTED) != e)
+ throw (e);
+ }
+
+ if ( act_res[0] == _resolution[0]
+ && act_res[1] == _resolution[1])
+ {
+ m_resize = 0;
+ return PISA_ERR_SUCCESS;
+ }
+
+ info->in_width = ::inch2width (_area[0], _resolution[0]);
+ info->in_height = ::inch2height (_area[1], _resolution[1]);
+ info->in_rowbytes = calc_rowbytes (info->in_width,
+ static_cast <pisa_pixel_type>
+ (_pixeltype));
+
+ info->bits_per_pixel = calc_bitperpix (static_cast <pisa_pixel_type>
+ (_pixeltype),
+ static_cast <pisa_bitdepth_type>
+ (_bitdepth));
+
+ if (_pixeltype == PISA_PT_BW)
+ info->resize_flag = PISA_RS_NN;
+ else
+ info->resize_flag = PISA_RS_BC;
+
+ return PISA_ERR_SUCCESS;
+}
+
+int
+scan_manager::init_moire (moire_img_info *info)
+{
+ info->resolution = _resolution[0] * _zoom[0] / 100;
+
+ _resolution[0] =
+ _resolution[1] =
+ iscan::moire::get_res_quote (info->resolution, !has_zoom ());
+
+ if (!has_zoom ())
+ {
+ try
+ {
+ get_valid_resolution (&_resolution[0], &_resolution[1]);
+ }
+ catch (const pisa_error& e)
+ {
+ if (pisa_error (PISA_STATUS_UNSUPPORTED) != e)
+ throw (e);
+ // else do nothing
+ }
+ }
+
+ info->in_resolution = _resolution[0];
+
+ _zoom [ 0 ] = 100;
+ _zoom [ 1 ] = 100;
+
+ info->in_width = ::inch2width (_area[0], _resolution[0]);
+ info->in_height = ::inch2height (_area[1], _resolution[1]);
+ info->in_rowbytes = calc_rowbytes (info->in_width,
+ static_cast <pisa_pixel_type>
+ (_pixeltype));
+
+ info->out_width = ::inch2width (_area[0], info->resolution);
+ info->out_height = ::inch2height (_area[1], info->resolution);
+ info->out_rowbytes = calc_rowbytes (info->out_width,
+ static_cast <pisa_pixel_type>
+ (_pixeltype));
+
+ info->bits_per_pixel = calc_bitperpix (static_cast <pisa_pixel_type>
+ (_pixeltype),
+ static_cast <pisa_bitdepth_type>
+ (_bitdepth));
+
+ return PISA_ERR_SUCCESS;
+}
+
+/*------------------------------------------------------------*/
+int
+scan_manager::init_sharp (sharp_img_info *info)
+{
+ long resolution;
+
+ if ( m_resize )
+ {
+ info->in_width = m_resize_info.out_width;
+ info->in_height = m_resize_info.out_height;
+
+ resolution = m_resize_info.resolution;
+ }
+ else if ( m_moire )
+ {
+ info->in_width = m_moire_info.out_width;
+ info->in_height = m_moire_info.out_height;
+
+ resolution = m_moire_info.resolution;
+ }
+ else
+ {
+ info->in_width = ::inch2width (_area[0], _resolution[0], _zoom[0]);
+ info->in_height = ::inch2height (_area[1], _resolution[1], _zoom[1]);
+
+ resolution = _resolution[0];
+ }
+
+ info->in_rowbytes = calc_rowbytes (info->in_width,
+ static_cast <pisa_pixel_type>
+ (_pixeltype));
+
+ info->bits_per_pixel = calc_bitperpix (static_cast <pisa_pixel_type>
+ (_pixeltype),
+ static_cast <pisa_bitdepth_type>
+ (_bitdepth));
+
+ info->out_width = info->in_width;
+ info->out_height = info->in_height;
+ info->out_rowbytes = info->in_rowbytes;
+
+ m_sharp_cls->set_parms (resolution,
+ using_tpu (),
+ !has_zoom (),
+ & info->strength,
+ & info->radius,
+ & info->clipping);
+
+ info->sharp_flag = PISA_SH_UMASK;
+
+ return PISA_ERR_SUCCESS;
+}
+
+/*------------------------------------------------------------*/
+int scan_manager::modify_img_info ( int * width, int * height )
+{
+ // resize
+ if ( m_resize )
+ {
+ if ( m_resize_info.in_width == m_resize_info.out_width )
+ {
+ m_resize_info.out_width = * width;
+ }
+ else if ( ( has_autocrop () && get_autocrop () )
+ || ( has_size_check () && get_size_check () ) )
+ {
+ m_resize_info.out_width =
+ (*width * m_resize_info.out_width + 0.5) / m_resize_info.in_width;
+ }
+ m_resize_info.out_rowbytes = ( m_resize_info.out_width *
+ m_resize_info.bits_per_pixel +
+ 7 ) / 8;
+
+ m_resize_info.in_width = * width;
+ m_resize_info.in_rowbytes = ( m_resize_info.in_width *
+ m_resize_info.bits_per_pixel +
+ 7 ) / 8;
+
+ if ( m_resize_info.in_height == m_resize_info.out_height )
+ {
+ m_resize_info.out_height = * height;
+ }
+ else if ( ( has_autocrop () && get_autocrop () )
+ || ( has_size_check () && get_size_check () ) )
+ {
+ m_resize_info.out_height =
+ (*height * m_resize_info.out_height + 0.5) / m_resize_info.in_height;
+ }
+
+ m_resize_info.in_height = * height;
+ }
+
+ if ( m_moire )
+ {
+ if ( ( has_autocrop () && get_autocrop () )
+ || ( has_size_check () && get_size_check () ) )
+ {
+ if ( m_moire_info.in_width == m_moire_info.out_width )
+ {
+ m_moire_info.out_width = * width;
+ }
+ else
+ {
+ m_moire_info.out_width =
+ (*width * m_moire_info.resolution + 0.5) / _resolution[0];
+ }
+ if ( m_moire_info.in_height == m_moire_info.out_height )
+ {
+ m_moire_info.out_height = * height;
+ }
+ else
+ {
+ m_moire_info.out_height =
+ (*height * m_moire_info.resolution + 0.5) / _resolution[1];
+ }
+ m_moire_info.in_width = * width;
+ m_moire_info.in_height = * height;
+ m_moire_info.in_rowbytes =
+ calc_rowbytes (m_moire_info.in_width,
+ static_cast <pisa_pixel_type> (_pixeltype));
+ m_moire_info.out_rowbytes =
+ calc_rowbytes (m_moire_info.out_width,
+ static_cast <pisa_pixel_type> (_pixeltype));
+ }
+ }
+
+ if ( m_sharp )
+ {
+ m_sharp_info.in_width = * width;
+ m_sharp_info.in_height = * height;
+ m_sharp_info.in_rowbytes = ( m_sharp_info.in_width *
+ m_sharp_info.bits_per_pixel +
+ 7 ) / 8;
+
+ if ( m_resize )
+ {
+ m_sharp_info.in_width = m_resize_info.out_width;
+ m_sharp_info.in_height = m_resize_info.out_height;
+ m_sharp_info.in_rowbytes = m_resize_info.out_rowbytes;
+ }
+
+ if ( m_moire )
+ {
+ m_sharp_info.in_width = m_moire_info.out_width;
+ m_sharp_info.in_height = m_moire_info.out_height;
+ m_sharp_info.in_rowbytes = m_moire_info.out_rowbytes;
+ }
+
+ m_sharp_info.out_width = m_sharp_info.in_width;
+ m_sharp_info.out_height = m_sharp_info.in_height;
+ m_sharp_info.out_rowbytes = m_sharp_info.in_rowbytes;
+ }
+
+ // update width and height
+ if ( m_resize )
+ {
+ * width = m_resize_info.out_width;
+ * height = m_resize_info.out_height;
+ }
+
+ if ( m_moire )
+ {
+ * width = m_moire_info.out_width;
+ * height = m_moire_info.out_height;
+ }
+
+ if ( m_sharp )
+ {
+ * width = m_sharp_info.out_width;
+ * height = m_sharp_info.out_height;
+ }
+
+
+ return PISA_ERR_SUCCESS;
+}
+
+/*------------------------------------------------------------*/
+int scan_manager::create_img_cls ( void )
+{
+ release_memory ();
+
+ if (m_sharp)
+ {
+ m_sharp_img_info = new IMAGE_INFO [ 2 ];
+
+ set_img_info ( & m_sharp_img_info [ _IN ],
+ & m_sharp_img_info [ _OUT ],
+ m_sharp_info );
+
+ m_sharp_cls = new iscan::focus (m_sharp_info);
+ }
+
+ if ( m_moire )
+ {
+ m_moire_img_info = new IMAGE_INFO [ 2 ];
+
+ set_img_info ( & m_moire_img_info [ _IN ],
+ & m_moire_img_info [ _OUT ],
+ m_moire_info );
+
+ m_moire_cls = new iscan::moire (m_moire_info, !has_zoom ());
+ }
+
+ if ( m_resize )
+ {
+ m_resize_img_info = new IMAGE_INFO [ 2 ];
+
+ set_img_info ( & m_resize_img_info [ _IN ],
+ & m_resize_img_info [ _OUT ],
+ m_resize_info );
+
+ m_resize_cls = new iscan::scale (m_resize_info);
+ }
+
+ return PISA_ERR_SUCCESS;
+}
+
+/*------------------------------------------------------------*/
+int scan_manager::release_memory ( void )
+{
+ if ( m_resize_cls )
+ delete m_resize_cls;
+ m_resize_cls = 0;
+
+ if ( m_moire_cls )
+ delete m_moire_cls;
+ m_moire_cls = 0;
+
+ if ( m_sharp_cls )
+ delete m_sharp_cls;
+ m_sharp_cls = 0;
+
+ if ( m_resize_img_info )
+ delete [] m_resize_img_info;
+ m_resize_img_info = 0;
+
+ if ( m_moire_img_info )
+ delete [] m_moire_img_info;
+ m_moire_img_info = 0;
+
+ if ( m_sharp_img_info )
+ delete [] m_sharp_img_info;
+ m_sharp_img_info = 0;
+
+ return PISA_ERR_SUCCESS;
+}
+
+/*------------------------------------------------------------*/
+void scan_manager::set_img_info ( LPIMAGE_INFO in_img_info,
+ LPIMAGE_INFO out_img_info,
+ const img_size & size )
+{
+ in_img_info->pImg_Buf = 0;
+ in_img_info->Img_Width = size.in_width;
+ in_img_info->Img_Height = size.in_height;
+ in_img_info->Img_RowBytes = size.in_rowbytes;
+ in_img_info->BitsPerPixel = size.bits_per_pixel;
+
+ out_img_info->pImg_Buf = 0;
+ out_img_info->Img_Width = size.out_width;
+ out_img_info->Img_Height = size.out_height;
+ out_img_info->Img_RowBytes = size.out_rowbytes;
+ out_img_info->BitsPerPixel = size.bits_per_pixel;
+}
+
+/*------------------------------------------------------------*/
+int scan_manager::get_send_in_line ( int out_line )
+{
+ size_t quote = out_line;
+
+ if (m_sharp)
+ {
+ m_sharp_img_info [ _OUT ].Img_Height = quote;
+ quote = m_sharp_cls->get_line_quote (quote);
+ m_sharp_img_info [ _IN ].Img_Height = quote;
+ }
+ if (m_moire)
+ {
+ m_moire_img_info [ _OUT ].Img_Height = quote;
+ quote = m_moire_cls->get_line_quote (quote);
+ m_moire_img_info [ _IN ].Img_Height = quote;
+ }
+ if (m_resize)
+ {
+ m_resize_img_info [ _OUT ].Img_Height = quote;
+ quote = m_resize_cls->get_line_quote (quote);
+ m_resize_img_info [ _IN ].Img_Height = quote;
+ }
+
+ return quote;
+}
+
+/*------------------------------------------------------------*/
+int scan_manager::image_process ( unsigned char * in_img,
+ unsigned char * out_img )
+{
+ unsigned char *inbuf, *outbuf;
+ size_t in_sz, out_sz;
+
+ unsigned char *tmpbuf = NULL;
+ size_t tmp_sz = 0;
+ bool zapbuf = false;
+
+ try
+ {
+ if (m_resize)
+ {
+ in_sz = m_resize_img_info [ _IN ].Img_Height * m_resize_img_info [ _IN].Img_RowBytes;
+ inbuf = in_img;
+
+ if (m_sharp)
+ {
+ out_sz = tmp_sz = (m_resize_img_info [ _OUT ].Img_Height
+ * m_resize_img_info [ _OUT ].Img_RowBytes);
+ outbuf = tmpbuf = new unsigned char [out_sz];
+ zapbuf = true;
+ }
+ else
+ {
+ out_sz = m_resize_img_info [ _OUT ].Img_Height * m_resize_img_info [ _OUT ].Img_RowBytes;
+ outbuf = out_img;
+ }
+ m_resize_cls->exec (inbuf, in_sz, outbuf, out_sz);
+ }
+
+ if (m_moire)
+ {
+ in_sz = m_moire_img_info [ _IN ].Img_Height * m_moire_img_info [ _IN ].Img_RowBytes;
+ inbuf = in_img;
+
+ if (m_sharp)
+ {
+ out_sz = tmp_sz = (m_moire_img_info [ _OUT ].Img_Height
+ * m_moire_img_info [ _OUT ].Img_RowBytes);
+ outbuf = tmpbuf = new unsigned char [out_sz];
+ zapbuf = true;
+ }
+ else
+ {
+ out_sz = m_moire_img_info [ _OUT ].Img_Height * m_moire_img_info [ _OUT ].Img_RowBytes;
+ outbuf = out_img;
+ }
+ m_moire_cls->exec (inbuf, in_sz, outbuf, out_sz);
+ }
+
+ if (m_sharp)
+ {
+ out_sz = m_sharp_img_info [ _OUT ].Img_Height * m_sharp_img_info [ _OUT ].Img_RowBytes;
+ outbuf = out_img;
+
+ if (m_resize || m_moire)
+ {
+ in_sz = tmp_sz;
+ inbuf = tmpbuf;
+ }
+ else
+ {
+ in_sz = m_sharp_img_info [ _IN ].Img_Height * m_sharp_img_info [ _IN ].Img_RowBytes;
+ inbuf = in_img;
+ }
+ m_sharp_cls->exec (inbuf, in_sz, outbuf, out_sz);
+ }
+
+ if (zapbuf)
+ {
+ delete [] tmpbuf;
+ }
+ }
+ catch (bad_alloc& oops)
+ {
+ if (zapbuf)
+ {
+ delete [] tmpbuf;
+ }
+ return PISA_ERR_OUTOFMEMORY;
+ }
+
+ return PISA_ERR_SUCCESS;
+}
+
+void
+scan_manager::adjust_scan_param (long *resolution, long *scale) const
+{
+ int min_res = 50;
+ int max_res = get_max_resolution ();
+ int adj_res = *resolution;
+
+ int min_scale = 50;
+ int max_scale = 200;
+ int adj_scale = 100; // assume no scaling is needed
+
+ if (adj_res < min_res)
+ {
+ adj_scale = adj_res * 100 / min_res;
+
+ if (adj_scale < min_scale)
+ {
+ adj_scale = min_scale;
+ }
+ adj_res = min_res;
+ }
+
+ if (max_res < adj_res)
+ {
+ adj_scale = adj_res * 100 / max_res;
+
+ if (adj_scale > max_scale)
+ {
+ adj_scale = max_scale;
+ }
+ adj_res = max_res;
+ }
+
+ *resolution = adj_res;
+ *scale = adj_scale;
+}
+
+/*! Sets main and sub resolutions to the best available value.
+
+ The best available value is the first resolution not smaller than
+ the value passed. If no such value is available, the largest
+ available resolution will be used unless \a use_max is \c false.
+
+ In the latter case an exception will be thrown instead.
+ */
+void
+scan_manager::get_valid_resolution (long int *x_res, long int *y_res,
+ bool use_max) const
+{
+ if (!x_res || !y_res)
+ throw pisa_error (PISA_ERR_PARAMETER);
+
+ SANE_Int res_x = get_resolution (SANE_NAME_SCAN_X_RESOLUTION, *x_res);
+ SANE_Int res_y = get_resolution (SANE_NAME_SCAN_Y_RESOLUTION, *y_res);
+
+ if (0 == res_x || 0 == res_y)
+ {
+ if (!use_max)
+ throw pisa_error (PISA_STATUS_UNSUPPORTED);
+ if (0 == res_x)
+ res_x = get_max_resolution (SANE_NAME_SCAN_X_RESOLUTION);
+ if (0 == res_y)
+ res_y = get_max_resolution (SANE_NAME_SCAN_Y_RESOLUTION);
+ }
+
+ *x_res = res_x;
+ *y_res = res_y;
+}
+
+static bool
+scaling_too_large (const img_size &info)
+{
+ return info.in_width > SCALING_INPUT_WIDTH_LIMIT
+ && info.in_width != info.out_width;
+}
+
+bool
+scan_manager::area_is_too_large (void) const
+{
+ int area_is_valid;
+
+ // due to a bug in esmod, need to limit the input width when scaling
+ if (m_resize && scaling_too_large (m_resize_info)) return true;
+ if (m_moire && scaling_too_large (m_moire_info)) return true;
+
+ get_value ("scan-area-is-valid", &area_is_valid);
+ return (0 == area_is_valid);
+}
+
+bool
+scan_manager::adf_duplex_direction_matches (void) const
+{
+ int rv;
+
+ // if option is inactive, assume direction does not match
+ if (!is_activate ("adf-duplex-direction-matches"))
+ return false;
+
+ try
+ {
+ get_value ("adf-duplex-direction-matches", &rv);
+ }
+ catch (pisa_error& oops)
+ { // if no option available, assume direction does not match
+ return false;
+ }
+ return (SANE_TRUE == rv);
+}
+
+void
+scan_manager::has_prev_img ( int has_prev )
+{
+ _has_prev_img = has_prev;
+}
+
+bool
+scan_manager::push_button_needs_polling (void) const
+{
+ SANE_Bool result = false;
+
+ if (is_activate ("needs-polling"))
+ get_value ("needs-polling", &result);
+
+ return result;
+}
+
+void
+scan_manager::update_settings (bool is_preview)
+{
+ SANE_Bool auto_scan = !is_preview;
+
+ set_scan_source ();
+
+ if (is_activate ("adf-auto-scan"))
+ set_value ("adf-auto-scan", &auto_scan);
+
+ _max_descreen_resolution = get_max_resolution (has_zoom () ? 800 : 600);
+
+ set_film_type ();
+ set_focus (_focus);
+ set_speed (_speed);
+ set_deskew (_deskew);
+ set_color_mode (_pixeltype, _bitdepth);
+
+ if (is_preview || _has_prev_img)
+ {
+ set_gamma_table (_gamma.gamma_r);
+ if (_pixeltype == PISA_PT_BW)
+ set_threshold (_threshold);
+ }
+ if (_pixeltype == PISA_PT_RGB)
+ set_color_profile (_coef);
+ set_brightness_method (br_iscan);
+ if (has_brightness())
+ {
+ set_brightness (_brightness);
+ }
+ if (has_contrast())
+ {
+ set_contrast (_contrast);
+ }
+ set_scan_resolution (_resolution[0], _resolution[1]);
+ set_scan_zoom (_zoom[0], _zoom[1]);
+ set_scan_area (_offset[0], _offset[1],
+ _area[0], _area[1]);
+}
+
+pisa_error_id
+scan_manager::set_scan_parameters (const settings& set, const marquee& marq)
+{
+ _pixeltype = set.imgtype.pixeltype;
+ _bitdepth = set.imgtype.bitdepth;
+ _dropout = set.imgtype.dropout;
+ _monoopt = set.imgtype.monoopt;
+ _halftone = set.imgtype.halftone;
+
+ _offset[0] = marq.offset.x;
+ _offset[1] = marq.offset.y;
+ _area[0] = marq.area.x;
+ _area[1] = marq.area.y;
+ _resolution[0]= (set.resolution * marq.scale + 50) / 100;
+ _resolution[1]= (set.resolution * marq.scale + 50) / 100;
+ _zoom[0] = 100;
+ _zoom[1] = 100;
+
+ _brightness = marq.brightness;
+ _contrast = marq.contrast;
+ _deskew = get_deskew();
+
+ for (int i = 0; i < 256; ++i)
+ {
+ _gamma.gamma_r[i] = marq.lut.gamma_r[i];
+ _gamma.gamma_g[i] = marq.lut.gamma_g[i];
+ _gamma.gamma_b[i] = marq.lut.gamma_b[i];
+ }
+
+ if (set.imgtype.pixeltype == PISA_PT_RGB)
+ {
+ generate_color_coef (_coef, set.coef, marq.saturation);
+ }
+ else // use identity matrix
+ { // FIXME: fold into generate_color_coef()
+ for (int i = 0; i < 9; ++i)
+ _coef[i] = 0.0;
+
+ _coef[0] = 1.0;
+ _coef[4] = 1.0;
+ _coef[8] = 1.0;
+ }
+
+ _threshold = marq.threshold;
+ _speed = set.enable_draft_mode;
+ _focus = marq.focus;
+
+ if (PISA_PT_BW == set.imgtype.pixeltype)
+ _usm = 0;
+ else
+ _usm = set.usm;
+
+ if (PISA_DESCREEN_ON == set.imgtype.de_screening)
+ _de_screening = 1;
+ else
+ _de_screening = 0;
+
+
+ // check for error conditions
+ pisa_error_id err = PISA_ERR_SUCCESS;
+
+ if (set.imgtype.de_screening == PISA_DESCREEN_ON)
+ {
+ if ( _resolution[0] > _max_descreen_resolution
+ || _resolution[1] > _max_descreen_resolution)
+ err = PISA_ERR_MRRESTOOHIGH;
+ }
+ return err;
+}
+
+#include "pisa_view_manager.h"
+pisa_error_id
+scan_manager::set_scan_parameters (const settings& set, const marquee& marq,
+ int resolution)
+{
+ _pixeltype = PISA_PT_RGB;
+ _bitdepth = PISA_BD_8;
+ _dropout = PISA_DO_NONE;
+ _monoopt = PISA_MO_NONE;
+ _halftone = PISA_HT_NONE;
+
+ _offset[0] = marq.offset.x;
+ _offset[1] = marq.offset.y;
+ _area[0] = marq.area.x;
+ _area[1] = marq.area.y;
+ _resolution[0]= resolution;
+ _resolution[1]= resolution;
+ _zoom[0] = 100;
+ _zoom[1] = 100;
+
+ _brightness = marq.brightness;
+ _contrast = marq.contrast;
+ _deskew = get_deskew();
+
+ // gamma table
+ for (int i = 0; i < 256; ++i)
+ {
+ _gamma.gamma_r[i] = i;
+ _gamma.gamma_g[i] = i;
+ _gamma.gamma_b[i] = i;
+ }
+
+ // profile matrix
+ for (int i = 0; i < 9; ++i)
+ _coef[i] = 0.0;
+
+ _coef[0] = 1.0;
+ _coef[4] = 1.0;
+ _coef[8] = 1.0;
+
+ _threshold = 0;
+ _speed = 1;
+ _focus = marq.focus;
+ _usm = 0;
+ _de_screening = 0;
+
+ return PISA_ERR_SUCCESS;
+}
diff --git a/frontend/pisa_scan_manager.h b/frontend/pisa_scan_manager.h
new file mode 100644
index 0000000..18002ef
--- /dev/null
+++ b/frontend/pisa_scan_manager.h
@@ -0,0 +1,151 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001, 2005, 2008, 2009 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_SCAN_MANAGER_H
+#define ___PISA_SCAN_MANAGER_H
+
+#include "esmod-wrapper.hh"
+
+#include "pisa_sane_scan.h"
+#include "pisa_error.h"
+
+class scan_manager : public sane_scan
+{
+ public:
+
+ // operator
+ void open_device ( char * name = 0 );
+
+ void init_preview (int *width, int *height);
+ void init_scan (int *width, int *height, bool reset_params = true);
+ void adjust_scan_param (long *resolution, long *scale) const;
+ void get_valid_resolution (long int *x_res, long int *y_res,
+ bool use_max = false) const;
+
+ void acquire_image ( unsigned char * img,
+ int row_bytes,
+ int height,
+ int cancel );
+ int release_memory ( void );
+
+ pisa_error_id set_scan_parameters (const settings& set,
+ const marquee& marq);
+ pisa_error_id set_scan_parameters (const settings& set,
+ const marquee& marq,
+ int resolution);
+
+ bool adf_duplex_direction_matches (void) const;
+
+ void has_prev_img ( int has_prev );
+
+ bool push_button_needs_polling (void) const;
+
+ protected:
+ void set_scan_parameters (bool is_preview = false);
+ void start_scan (int *width, int *height);
+
+ private:
+
+ typedef struct _IMAGE_INFO {
+ unsigned char* pImg_Buf;
+ long Img_Width;
+ long Img_Height;
+ unsigned long Img_RowBytes;
+ short BitsPerPixel;
+ } IMAGE_INFO, *LPIMAGE_INFO;
+
+ // operation
+ int init_img_process_info (void);
+ int init_zoom (resize_img_info *info);
+ int init_moire (moire_img_info *info);
+ int init_sharp (sharp_img_info *info);
+
+ int modify_img_info ( int * width, int * height );
+
+ int create_img_cls ( void );
+
+ void set_img_info ( LPIMAGE_INFO in_img_info,
+ LPIMAGE_INFO out_img_info,
+ const img_size & size );
+
+ void update_settings (bool is_preview);
+ bool area_is_too_large (void) const;
+
+ // FIXME: the following block of member functions should not be the
+ // responsibility of the scan_manager. They can be removed
+ // once the filter class hierarchy has support for chaining
+ // of filters and this class no longer needs to baby sit the
+ // data transfers.
+ int get_send_in_line ( int out_line );
+ int image_process ( unsigned char * in_img, unsigned char * out_img );
+
+ // for image module
+ long m_resize;
+ long m_moire;
+ long m_sharp;
+
+ resize_img_info m_resize_info;
+ moire_img_info m_moire_info;
+ sharp_img_info m_sharp_info;
+
+ iscan::scale * m_resize_cls;
+ iscan::moire * m_moire_cls;
+ iscan::focus * m_sharp_cls;
+
+ enum { _IN, _OUT };
+ IMAGE_INFO * m_resize_img_info;
+ IMAGE_INFO * m_moire_img_info;
+ IMAGE_INFO * m_sharp_img_info;
+
+ char _pixeltype;
+ char _bitdepth;
+ char _dropout;
+ char _monoopt;
+ char _halftone;
+ double _offset[2];
+ double _area[2];
+ long _resolution[2];
+ long _zoom[2];
+ gamma_struct _gamma;
+ double _coef[9];
+ long _threshold;
+ long _speed;
+ long _focus;
+ long _usm;
+ long _brightness;
+ long _contrast;
+ long _deskew;
+ long _de_screening;
+ long _max_descreen_resolution;
+ int _has_prev_img;
+};
+
+#endif // ___PISA_SCAN_MANAGER_H
+
diff --git a/frontend/pisa_scan_selector.cc b/frontend/pisa_scan_selector.cc
new file mode 100644
index 0000000..8eea853
--- /dev/null
+++ b/frontend/pisa_scan_selector.cc
@@ -0,0 +1,503 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2003, 2008, 2009 SEIKO EPSON CORPORATION
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#include <config.h>
+
+#include "gettext.h"
+#define _(msg_id) gettext (msg_id)
+
+/*------------------------------------------------------------*/
+#include <gtk/gtk.h>
+#include <cstdlib>
+#include <cstdio>
+#include <cstring>
+#include <regex.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
+#include <sane/sane.h>
+#include <sane/saneopts.h>
+#ifdef __cplusplus
+}
+#endif
+#include "pisa_error.h"
+#include "pisa_main.h"
+#include "pisa_scan_selector.h"
+#include "pisa_view_manager.h"
+#include "pisa_gimp.h"
+/*------------------------------------------------------------*/
+
+static void
+_delete( GtkWidget *widget, scan_selector *ss )
+{
+ ss->cancel(); // same process as cancel
+}
+
+static void
+_select( GtkEditable *, scan_selector *ss )
+{
+ ss->select(); // just forward the call
+}
+
+static void
+_update( GtkButton *, scan_selector *ss )
+{
+ ss->update(); // just forward the call
+}
+
+static void
+_cancel( GtkButton *, scan_selector *ss )
+{
+ ss->cancel(); // just forward the call
+}
+
+
+scan_selector::~scan_selector()
+{
+ free( _current_gui );
+ free( _current_dev );
+ g_slist_free( _gui_names );
+ g_slist_free( _dev_names );
+ g_slist_free( _items );
+ ::gtk_widget_destroy( m_dbox );
+}
+
+scan_selector::scan_selector( bool is_dialog )
+ : m_local_only( SANE_FALSE ),
+ m_dbox( 0 ), m_hbox( 0 ), m_menu( 0 ), _items( 0 ),
+ _dev_names( 0 ), _gui_names( 0 ),
+ _current_dev( 0 ), _current_gui( 0 )
+{
+ GtkWidget *vbox = 0; // dialog container
+
+ if (is_dialog)
+ {
+ { // dialog box
+ GtkWidget *w = ::gtk_dialog_new ();
+ ::gtk_object_set_data( GTK_OBJECT( w ), "m_dbox", w );
+ ::gtk_window_set_title( GTK_WINDOW( w ),
+ _("Scan Selector Dialog") );
+
+#ifdef HAVE_GTK_2
+ GTK_WINDOW( w )->type = GTK_WINDOW_TOPLEVEL;
+#else
+ GTK_WINDOW( w )->type = GTK_WINDOW_DIALOG;
+#endif
+ ::gtk_window_set_modal( GTK_WINDOW( w ), TRUE );
+ ::gtk_window_set_policy( GTK_WINDOW( w ), FALSE, TRUE, FALSE );
+
+ ::gtk_signal_connect( GTK_OBJECT( w ), "delete_event",
+ GTK_SIGNAL_FUNC( ::_delete ), this );
+
+ m_dbox = w;
+ }
+
+ {
+ GtkWidget *w = GTK_DIALOG( m_dbox )->vbox;
+ ::gtk_object_set_data( GTK_OBJECT( m_dbox ), "vbox", w );
+ ::gtk_container_set_border_width( GTK_CONTAINER( w ), 5 );
+ ::gtk_widget_show( w );
+
+ vbox = w;
+ }
+ }
+
+ { // selector container
+ GtkWidget *w = ::gtk_hbox_new( FALSE, 5 );
+ ::gtk_widget_ref( w );
+ ::gtk_object_set_data_full( GTK_OBJECT( w ), "m_hbox", w,
+ (GtkDestroyNotify) ::gtk_widget_unref );
+ if (vbox) // only when in a dialog
+ ::gtk_box_pack_start( GTK_BOX( vbox ), w, TRUE, TRUE, 0 );
+ ::gtk_container_set_border_width( GTK_CONTAINER( w ), 5 );
+
+ m_hbox = w;
+ }
+
+ { // scanner label
+ GtkWidget *w = ::gtk_label_new (_("Scanner:"));
+ ::gtk_box_pack_start( GTK_BOX( m_hbox ), w, FALSE, FALSE, 0 );
+ ::gtk_widget_ref( w );
+ ::gtk_object_set_data_full( GTK_OBJECT (m_hbox), "label", w,
+ (GtkDestroyNotify) ::gtk_widget_unref );
+ ::gtk_widget_show( w );
+ }
+
+ { // option menu
+ GtkWidget *w = ::gtk_option_menu_new ();
+ ::gtk_box_pack_start( GTK_BOX( m_hbox ), w, FALSE, FALSE, 0 );
+ ::gtk_widget_ref( w );
+ ::gtk_object_set_data_full( GTK_OBJECT( m_hbox ), "option", w,
+ (GtkDestroyNotify) ::gtk_widget_unref );
+ ::gtk_widget_show( w );
+
+ m_opts = w;
+ }
+
+ if (!is_dialog)
+ { // update button
+ GtkWidget *w = ::gtk_button_new_with_label( _(" Update ") );
+ ::gtk_container_set_border_width( GTK_CONTAINER( w ), 5 );
+ ::gtk_box_pack_start( GTK_BOX( m_hbox ), w, FALSE, FALSE, 0 );
+ ::gtk_widget_ref( w );
+ ::gtk_object_set_data_full( GTK_OBJECT( m_hbox ), "update", w,
+ (GtkDestroyNotify) ::gtk_widget_unref );
+ ::gtk_widget_show( w );
+
+ ::gtk_signal_connect( GTK_OBJECT( w ), "clicked",
+ GTK_SIGNAL_FUNC( ::_update ), this );
+ }
+ else
+ {
+ GtkWidget *action_area;
+ { // dialog action button area
+ GtkWidget *w = GTK_DIALOG (m_dbox)->action_area;
+ ::gtk_object_set_data( GTK_OBJECT( m_dbox ), "action_area", w );
+ ::gtk_container_set_border_width( GTK_CONTAINER( w ), 5 );
+ ::gtk_widget_show( w );
+
+ action_area = w;
+ }
+
+ GtkWidget *button_box;
+ {
+ GtkWidget *w = gtk_hbutton_box_new();
+ gtk_widget_ref( w );
+ ::gtk_object_set_data_full( GTK_OBJECT( m_dbox ), "button_box", w,
+ (GtkDestroyNotify) gtk_widget_unref );
+ ::gtk_box_pack_start( GTK_BOX( action_area ), w, TRUE, FALSE, 0 );
+ ::gtk_widget_show( w );
+
+ button_box = w;
+ }
+
+ { // okay button
+ GtkWidget *w = ::gtk_button_new_with_label( _(" OK ") );
+ ::gtk_widget_ref( w );
+ ::gtk_object_set_data_full( GTK_OBJECT( m_dbox ), "okay_button", w,
+ (GtkDestroyNotify) gtk_widget_unref );
+ ::gtk_box_pack_start( GTK_BOX( button_box ), w, TRUE, FALSE, 0 );
+ GTK_WIDGET_SET_FLAGS( w, GTK_CAN_DEFAULT );
+ ::gtk_widget_grab_default( w );
+ ::gtk_widget_show( w );
+
+ ::gtk_signal_connect( GTK_OBJECT( w ), "clicked",
+ GTK_SIGNAL_FUNC( ::_select ), this );
+ }
+
+ { // cancel button
+ GtkWidget *w = ::gtk_button_new_with_label( _(" Cancel ") );
+ ::gtk_widget_ref( w );
+ ::gtk_object_set_data_full( GTK_OBJECT( m_dbox ), "cancel_button", w,
+ (GtkDestroyNotify) gtk_widget_unref );
+ ::gtk_box_pack_start( GTK_BOX( button_box ), w, TRUE, FALSE, 0 );
+ ::gtk_widget_show( w );
+
+ ::gtk_signal_connect( GTK_OBJECT( w ), "clicked",
+ GTK_SIGNAL_FUNC( ::_cancel ), this );
+ }
+ }
+}
+
+void
+scan_selector::show() const
+{
+ gtk_widget_show( m_hbox );
+ if (m_dbox)
+ gtk_widget_show( m_dbox );
+}
+
+GtkWidget *
+scan_selector::widget() const
+{
+ return (m_dbox ? m_dbox : m_hbox);
+}
+
+void
+scan_selector::update()
+{
+ const SANE_Device **device;
+ SANE_Status status;
+ status = sane_get_devices ( &device, m_local_only );
+
+ if (SANE_STATUS_GOOD != status)
+ throw pisa_error( status );
+
+ // prepare a new menu
+ GtkWidget *menu = gtk_menu_new();
+ GSList *items = 0;
+ GSList *dev_names = 0;
+ GSList *gui_names = 0;
+
+ int cnt = 0;
+ while (*device)
+ {
+ char *name = rewrite_name( *device );
+ if (name)
+ {
+ GtkWidget *item = gtk_menu_item_new ();
+ GtkWidget *label = gtk_label_new( name );
+ gtk_label_set_line_wrap( GTK_LABEL( label ), TRUE );
+ gtk_container_add( GTK_CONTAINER(item), label );
+ ++cnt;
+
+ if (!m_dbox)
+ gtk_signal_connect( GTK_OBJECT( item ), "activate",
+ GTK_SIGNAL_FUNC( _select ), this );
+
+ items = g_slist_append( items, item );
+ dev_names = g_slist_append( dev_names,
+ (void *) ((*device)->name) );
+ gui_names = g_slist_append( gui_names, (void *) name );
+
+ gtk_menu_append( GTK_MENU( menu ), item );
+ gtk_widget_show( label );
+ gtk_widget_show( item );
+ }
+ ++device;
+ }
+
+ if (!cnt)
+ throw pisa_error( PISA_ERR_CONNECT );
+
+ if (m_menu) // get rid of the old menu
+ gtk_option_menu_remove_menu( GTK_OPTION_MENU( m_opts ) );
+
+ m_menu = menu;
+ gtk_option_menu_set_menu( GTK_OPTION_MENU( m_opts ), m_menu );
+
+ if (_items)
+ {
+ GSList *item;
+ if (!m_dbox)
+ for (item = _items; item; item = item->next )
+ gtk_signal_disconnect_by_func( GTK_OBJECT( item->data ),
+ GTK_SIGNAL_FUNC( _select ), this );
+ g_slist_free( _items );
+ }
+ if (_dev_names)
+ g_slist_free( _dev_names );
+ if (_gui_names)
+ g_slist_free( _gui_names );
+
+ _items = items;
+ _dev_names = dev_names;
+ _gui_names = gui_names;
+
+ if (!_current_dev)
+ {
+ char *name = (char *) _dev_names->data;
+ _current_dev = (char *) malloc( strlen( name ) + 1 );
+ if (!_current_dev)
+ throw pisa_error( PISA_STATUS_NO_MEM );
+ strcpy( _current_dev, name );
+ }
+ if (!_current_gui)
+ {
+ char *name = (char *) _gui_names->data;
+ _current_gui = (char *) malloc( strlen( name ) + 1 );
+ if (!_current_gui)
+ throw pisa_error( PISA_STATUS_NO_MEM );
+ strcpy( _current_gui, name );
+ }
+}
+
+void
+scan_selector::cancel()
+{
+ ::gtk_main_quit();
+ sane_exit ();
+ if( pisa_gimp_plugin() )
+ pisa_gimp_quit();
+ exit( 0 );
+}
+
+void
+scan_selector::select()
+{
+ GtkWidget *item = gtk_menu_get_active( GTK_MENU( m_menu ) );
+
+ gint pos = g_slist_position( _items, g_slist_find( _items, item ) );
+ if (-1 == pos)
+ {
+ fprintf( stderr, "scan_selector: internal inconsistency\n" );
+ abort();
+ }
+
+ char *dev_name = (char *) g_slist_nth_data( _dev_names, (guint) pos );
+ char *gui_name = (char *) g_slist_nth_data( _gui_names, (guint) pos );
+
+ if (0 != strcmp( _current_dev, dev_name ))
+ {
+ char *new_name = (char *) malloc( (strlen( dev_name ) + 1)
+ * sizeof( char ) );
+ if (!new_name)
+ throw pisa_error( PISA_STATUS_NO_MEM );
+
+ strcpy( new_name, dev_name );
+ free( _current_dev );
+ _current_dev = new_name;
+ }
+ if (0 != strcmp( _current_gui, gui_name ))
+ {
+ char *new_name = (char *) malloc( (strlen( gui_name ) + 1)
+ * sizeof( char ) );
+ if (!new_name)
+ throw pisa_error( PISA_STATUS_NO_MEM );
+
+ strcpy( new_name, gui_name );
+ free( _current_gui );
+ _current_gui = new_name;
+ }
+
+ if (m_dbox)
+ {
+ ::gtk_widget_hide( m_dbox );
+ ::gtk_main_quit();
+ }
+ else
+ g_view_manager->set_device( dev_name );
+}
+
+char *
+scan_selector::get_device( bool rewrite )
+{
+ if (m_dbox && !rewrite)
+ {
+ update();
+ if (1 < g_slist_length( _items ))
+ {
+ show();
+ ::gtk_grab_add( m_dbox );
+ ::gtk_main();
+ ::gtk_grab_remove( m_dbox );
+ }
+ }
+ return (rewrite
+ ? _current_gui // GUI device name
+ : _current_dev); // raw device name
+}
+
+char *
+scan_selector::rewrite_name( const SANE_Device *device )
+{
+ if (!device)
+ return 0;
+
+ if (0 != strcmp( device->vendor, "Epson" ))
+ return 0; // sorry 'bout that, everyone ;-}
+
+ char *name = const_cast< char * >( device->name );
+ if (!name)
+ return 0;
+
+ // disable host-base scanners accessed
+ // through the net backend (they crash
+ // way too often)
+ if (0 == strncmp( name, "net:", strlen( "net:" ) )
+ && ( 0 == strcmp( device->model, "GT-7200" )
+ || 0 == strcmp( device->model, "GT-7300" )
+ || 0 == strcmp( device->model, "Perfection1250" )
+ || 0 == strcmp( device->model, "Perfection1260" )
+ ))
+ return 0;
+
+ { // dropping other backend EPSON devices
+ regex_t *comp_regex = new regex_t;
+ const char *regex = "^(net:([^:]+:))?(epson|epson2|plustek|snapscan):(.*)$";
+ int comp = regcomp( comp_regex, regex, REG_EXTENDED );
+
+ if (0 == comp)
+ {
+ int result = regexec( comp_regex, device->name, 0, 0, 0 );
+ if (0 == result)
+ {
+ name = 0; // other backend controlled device
+ }
+ else
+ if (REG_NOMATCH != result)
+ regerror( comp, comp_regex );
+ }
+ else
+ regerror( comp, comp_regex );
+
+ regfree( comp_regex );
+ delete comp_regex;
+
+ if (!name)
+ return 0; // sorry 'bout that everyone ;-}
+ }
+
+ // if we are still here we have an epkowa backend supported EPSON
+ // scanner OR, heaven forbid, an aliased scanner
+
+ {
+ // rewrite things into a string of the following form:
+ // "device->vendor device->model [device->name]"
+
+ size_t length = (strlen (device->vendor) + 1 // " "
+ + strlen (device->model) + 2 // " ["
+ + strlen (device->name) + 1 + 1); // "]" + \0
+
+ name = (char *) malloc( length * sizeof( char ) );
+ if (!name)
+ throw pisa_error( PISA_STATUS_NO_MEM );
+
+ strncpy (name, device->vendor, strlen (device->vendor));
+
+ char *c = name + strlen (device->vendor);
+ *c++ = ' ';
+ {
+ const char *p = device->model;
+ while (*p)
+ *c++ = *p++;
+ }
+ *c++ = ' ';
+ *c++ = '[';
+ { // copy device name
+ const char *p = device->name;
+ while (*p)
+ *c++ = *p++;
+ }
+ *c++ = ']';
+ *c = '\0';
+ }
+
+ return name;
+}
+
+void
+scan_selector::regerror( int code, regex_t *regex )
+{
+ size_t length = ::regerror( code, regex, 0, 0 );
+ char *message = new char[length];
+
+ ::regerror( code, regex, message, length );
+ fprintf( stderr, "%s\n", message );
+
+ delete[] message;
+}
diff --git a/frontend/pisa_scan_selector.h b/frontend/pisa_scan_selector.h
new file mode 100644
index 0000000..ceee58f
--- /dev/null
+++ b/frontend/pisa_scan_selector.h
@@ -0,0 +1,72 @@
+/* -*- C++ -*-
+ SANE EPSON backend
+ Copyright (C) 2003 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 2003-03-13 S.Wong New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_SCAN_SELECTOR_H
+#define ___PISA_SCAN_SELECTOR_H
+
+#include <gtk/gtk.h>
+#include <regex.h>
+
+class scan_selector
+{
+public:
+ ~scan_selector();
+ scan_selector( bool is_dialog = false );
+
+ void show() const;
+
+ GtkWidget * widget() const;
+
+ void update();
+ void cancel();
+ void select();
+ char * get_device( bool rewrite = false );
+
+private:
+
+ char * rewrite_name( const SANE_Device *device );
+ void regerror( int code, regex_t *regex );
+
+ bool m_local_only;
+
+ GtkWidget *m_dbox;
+ GtkWidget *m_hbox;
+ GtkWidget *m_opts;
+ GtkWidget *m_menu;
+
+ GSList *_items;
+ GSList *_dev_names;
+ GSList *_gui_names;
+
+ char *_current_dev;
+ char *_current_gui;
+};
+
+#endif // ___PISA_SCAN_SELECTOR_H
diff --git a/frontend/pisa_scan_tool.cc b/frontend/pisa_scan_tool.cc
new file mode 100644
index 0000000..2ebc577
--- /dev/null
+++ b/frontend/pisa_scan_tool.cc
@@ -0,0 +1,591 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001, 2005, 2008 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+/*------------------------------------------------------------*/
+#include <stdio.h>
+#include <string.h>
+
+/*------------------------------------------------------------*/
+#include "pisa_scan_tool.h"
+#include "pisa_error.h"
+#include "pisa_default_val.h"
+
+/*------------------------------------------------------------*/
+static int build_lineart ( pisa_image_info * info,
+ unsigned char do_clr,
+ long threshold );
+static int build_halftone ( pisa_image_info * info,
+ unsigned char do_clr,
+ unsigned char halftone,
+ long threshold );
+static int build_dither ( pisa_image_info * info,
+ unsigned char do_clr,
+ unsigned char halftone,
+ long threshold );
+static unsigned char dropout ( unsigned char red,
+ unsigned char grn,
+ unsigned char blu,
+ unsigned char do_clr,
+ long thdiff = 0 );
+
+/*------------------------------------------------------------*/
+int calc_rowbytes ( int width, pisa_pixel_type type )
+{
+ int rowbytes;
+
+ rowbytes = width;
+
+ switch ( type )
+ {
+ case PISA_PT_BW:
+ rowbytes = ( width + 7 ) / 8;
+ break;
+
+ case PISA_PT_GRAY:
+ rowbytes = width;
+ break;
+
+ case PISA_PT_RGB:
+ rowbytes = width * 3;
+ break;
+ }
+
+ return rowbytes;
+}
+
+/*------------------------------------------------------------*/
+int calc_bitperpix ( pisa_pixel_type pixel, pisa_bitdepth_type bitdepth )
+{
+ int bitperpix;
+
+ bitperpix = 8;
+ bitdepth = bitdepth;
+
+ switch ( pixel )
+ {
+ case PISA_PT_BW:
+ bitperpix = 1;
+ break;
+
+ case PISA_PT_GRAY:
+ bitperpix = 8;
+ break;
+
+ case PISA_PT_RGB:
+ bitperpix = 24;
+ break;
+ }
+
+ return bitperpix;
+}
+
+/*------------------------------------------------------------*/
+int tool_lut ( pisa_image_info * info,
+ const gamma_struct * lut )
+{
+ int i, j;
+ unsigned char * img;
+
+ for ( i = 0; i < info->m_height; i++ )
+ {
+ img = info->m_img + i * info->m_rowbytes;
+
+ for ( j = 0; j < info->m_width; j++ )
+ {
+ // red
+ * img = lut->gamma_r [ * img ];
+ img++;
+ // green
+ * img = lut->gamma_g [ * img ];
+ img++;
+ // blue
+ * img = lut->gamma_b [ * img ];
+ img++;
+ }
+ }
+
+ return PISA_ERR_SUCCESS;
+}
+
+
+/*------------------------------------------------------------*/
+int tool_matrix ( pisa_image_info * info,
+ int saturation,
+ const double * color_profile )
+{
+ int i, j;
+ int color [ 9 ];
+ double color_corr [ 9 ];
+ int red, grn, blu;
+ int result_red, result_grn, result_blu;
+ unsigned char * img;
+ int max;
+
+ max = 0xff << 10;
+
+ generate_color_coef ( color_corr, color_profile, saturation );
+
+ for ( i = 0; i < 9; i++ )
+ color [ i ] = ( int ) ( color_corr [ i ] * ( 1 << 10 ) );
+
+ for ( i = 0; i < info->m_height; i++ )
+ {
+ img = info->m_img + i * info->m_rowbytes;
+
+ for ( j = 0; j < info->m_width; j++ )
+ {
+ red = * ( img + 0 );
+ grn = * ( img + 1 );
+ blu = * ( img + 2 );
+
+ result_red = ( int ) ( color [ 0 ] * red + color [ 1 ] * grn + color [ 2 ] * blu );
+ result_grn = ( int ) ( color [ 3 ] * red + color [ 4 ] * grn + color [ 5 ] * blu );
+ result_blu = ( int ) ( color [ 6 ] * red + color [ 7 ] * grn + color [ 8 ] * blu );
+
+ if ( result_red < 0 ) result_red = 0;
+ if ( result_red > max ) result_red = max;
+ if ( result_grn < 0 ) result_grn = 0;
+ if ( result_grn > max ) result_grn = max;
+ if ( result_blu < 0 ) result_blu = 0;
+ if ( result_blu > max ) result_blu = max;
+
+ * ( img + 0 ) = result_red >> 10;
+ * ( img + 1 ) = result_grn >> 10;
+ * ( img + 2 ) = result_blu >> 10;
+
+ img += 3;
+ }
+ }
+
+ return PISA_ERR_SUCCESS;
+}
+
+/*------------------------------------------------------------*/
+int generate_color_coef ( double * dst,
+ const double * src,
+ int saturation )
+{
+ int i, j;
+ int color_corr [ 3 ] [ 3 ];
+ double tmp_color [ 3 ] [ 3 ];
+ int val;
+
+ // build saturate
+ val = 300 + 2 * saturation;
+
+ for ( i = 0; i < 3; i++ )
+ for ( j = 0; j < 3; j++ )
+ color_corr [ i ] [ j ] = -saturation;
+
+ color_corr [ 0 ] [ 0 ] =
+ color_corr [ 1 ] [ 1 ] =
+ color_corr [ 2 ] [ 2 ] = val;
+
+ // build color filter
+ for ( i = 0; i < 3; i++ )
+ for ( j = 0; j < 3; j++ )
+ {
+ tmp_color [ i ] [ j ] = color_corr [ 0 ] [ j ] * src [ i * 3 + 0 ];
+ tmp_color [ i ] [ j ] += color_corr [ 1 ] [ j ] * src [ i * 3 + 1 ];
+ tmp_color [ i ] [ j ] += color_corr [ 2 ] [ j ] * src [ i * 3 + 2 ];
+ }
+
+ for ( i = 0; i < 3; i++ )
+ for ( j = 0; j < 3; j++ )
+ dst [ i * 3 + j ] = tmp_color [ i ] [ j ] / 300.0;
+
+ return PISA_ERR_SUCCESS;
+}
+
+
+/*------------------------------------------------------------*/
+int build_gray ( pisa_image_info * info,
+ unsigned char do_clr )
+{
+ long i, j;
+ unsigned char val;
+ unsigned char * img;
+
+ for ( i = 0; i < info->m_height; i++ )
+ {
+ img = info->m_img + i * info->m_rowbytes;
+ for ( j = 0; j < info->m_width; j++ )
+ {
+ val = dropout ( * ( img + 0 ), * ( img + 1 ), * ( img + 2 ),
+ do_clr );
+ * ( img + 0 ) = val;
+ * ( img + 1 ) = val;
+ * ( img + 2 ) = val;
+ img += 3;
+ }
+ }
+
+ return PISA_ERR_SUCCESS;
+}
+
+
+/*------------------------------------------------------------*/
+int build_bw ( pisa_image_info * info,
+ unsigned char do_clr,
+ unsigned char halftone,
+ long threshold )
+{
+ int ret;
+
+ ret = PISA_ERR_PARAMETER;
+
+ switch ( halftone )
+ {
+ case PISA_HT_NONE:
+ ret = build_lineart ( info, do_clr, threshold );
+ break;
+
+ case PISA_HT_TONEA:
+ case PISA_HT_TONEB:
+ case PISA_HT_TONEC:
+ ret = build_halftone ( info, do_clr, halftone, threshold );
+ break;
+
+ case PISA_HT_DITHERA:
+ case PISA_HT_DITHERB:
+ case PISA_HT_DITHERC:
+ case PISA_HT_DITHERD:
+ ret = build_dither ( info, do_clr, halftone, threshold );
+ break;
+ }
+
+ return ret;
+}
+
+
+
+/*------------------------------------------------------------*/
+static int build_lineart ( pisa_image_info * info,
+ unsigned char do_clr,
+ long threshold )
+{
+ int i, j;
+ unsigned char val;
+ unsigned char * img;
+ long thdiff;
+
+ thdiff = 128 - threshold;
+
+ for ( i = 0; i < info->m_height; i++ )
+ {
+ img = info->m_img + i * info->m_rowbytes;
+ for ( j = 0; j < info->m_width; j++ )
+ {
+ val = dropout ( * img, * ( img + 1 ), * ( img + 2 ),
+ do_clr, thdiff );
+ if ( val > 128 )
+ val = 255;
+ else
+ val = 0;
+
+ * ( img + 0 ) = val;
+ * ( img + 1 ) = val;
+ * ( img + 2 ) = val;
+ img += 3;
+ }
+ }
+
+ return PISA_ERR_SUCCESS;
+}
+
+
+/*------------------------------------------------------------*/
+static long Hard [ 8 ] [ 8 ] =
+{
+ { 128, 128, 128, 128, 128, 128, 128, 128 },
+ { 128, 128, 128, 128, 128, 128, 128, 128 },
+ { 128, 128, 128, 128, 128, 128, 128, 128 },
+ { 128, 128, 128, 128, 128, 128, 128, 128 },
+ { 128, 128, 128, 128, 128, 128, 128, 128 },
+ { 128, 128, 128, 128, 128, 128, 128, 128 },
+ { 128, 128, 128, 128, 128, 128, 128, 128 },
+ { 128, 128, 128, 128, 128, 128, 128, 128 }
+};
+
+static long Soft [ 8 ] [ 8 ] =
+{
+ { 131, 136, 136, 131, 124, 119, 119, 124 },
+ { 136, 149, 149, 136, 119, 106, 106, 119 },
+ { 136, 149, 149, 136, 119, 106, 106, 119 },
+ { 128, 128, 128, 128, 128, 128, 128, 128 },
+ { 131, 136, 136, 131, 124, 119, 119, 124 },
+ { 124, 119, 119, 124, 131, 136, 136, 131 },
+ { 119, 106, 106, 119, 136, 149, 149, 136 },
+ { 124, 119, 119, 124, 131, 136, 136, 131 }
+};
+
+static long Net [ 8 ] [ 8 ] =
+{
+ { 146, 173, 173, 146, 109, 82, 82, 109 },
+ { 173, 236, 236, 173, 82, 19, 19, 82 },
+ { 173, 236, 236, 173, 82, 19, 19, 82 },
+ { 146, 173, 173, 146, 109, 82, 82, 109 },
+ { 109, 82, 82, 109, 148, 173, 173, 146 },
+ { 82, 19, 19, 82, 173, 236, 236, 173 },
+ { 82, 19, 19, 82, 173, 236, 236, 173 },
+ { 109, 89, 82, 109, 146, 173, 173, 146 }
+};
+
+static long Bayer [ 4 ] [ 4 ] =
+{
+ { 248, 120, 216, 88 },
+ { 56, 184, 24, 152 },
+ { 200, 72, 232, 104 },
+ { 8, 136, 40, 168 }
+};
+
+static long Spiral [ 4 ] [ 4 ] =
+{
+ { 40, 152, 136, 24 },
+ { 168, 248, 232, 120 },
+ { 184, 200, 216, 104 },
+ { 56, 72, 88, 8 }
+};
+
+static long Net4x4 [ 4 ] [ 4 ] =
+{
+ { 24, 40, 152, 104 },
+ { 56, 248, 232, 136 },
+ { 168, 200, 216, 88 },
+ { 120, 184, 72, 8 }
+};
+
+static long Net8x8 [ 8 ] [ 8 ] =
+{
+ { 236, 188, 52, 4, 68, 100, 164, 228 },
+ { 180, 44, 12, 140, 132, 92, 108, 172 },
+ { 36, 20, 148, 212, 204, 124, 84, 76 },
+ { 28, 156, 220, 252, 244, 196, 116, 60 },
+ { 68, 100, 164, 228, 236, 188, 52, 4 },
+ { 132, 92, 108, 172, 180, 44, 12, 140 },
+ { 204, 124, 84, 76, 36, 20, 148, 212 },
+ { 244, 196, 116, 60, 28, 156, 220, 252 }
+};
+
+static struct _halftone
+{
+ long * matrix;
+ long size;
+} proc_halftone [ 8 ] =
+{
+ { 0, 8 },
+ { Hard [ 0 ], 8 },
+ { Soft [ 0 ], 8 },
+ { Net [ 0 ], 8 },
+ { Bayer [ 0 ], 4 },
+ { Spiral [ 0 ], 4 },
+ { Net4x4 [ 0 ], 4 },
+ { Net8x8 [ 0 ], 8 }
+};
+
+static int build_halftone ( pisa_image_info * info,
+ unsigned char do_clr,
+ unsigned char halftone,
+ long threshold )
+{
+ int i, j, k;
+ long sc1, sc2, sc3, sc4, sc5, sc6, long_val;
+ unsigned char val;
+ unsigned char * img;
+ long thdiff, thre;
+ long * pattern;
+ long matrix_size;
+ long * rowa, * rowb, * rowc;
+
+ thdiff = 128 - threshold;
+
+ pattern = proc_halftone [ halftone ].matrix;
+ if ( pattern == 0 )
+ return PISA_ERR_PARAMETER;
+
+ matrix_size = proc_halftone [ halftone ].size;
+
+ rowa = new long [ info->m_width + 10 ];
+ rowb = new long [ info->m_width + 10 ];
+ rowc = new long [ info->m_width + 10 ];
+
+ ::memset ( rowa, 0, ( info->m_width + 10 ) * sizeof ( long ) );
+ ::memset ( rowb, 0, ( info->m_width + 10 ) * sizeof ( long ) );
+ ::memset ( rowc, 0, ( info->m_width + 10 ) * sizeof ( long ) );
+
+ for ( i = 0; i < info->m_height; i++ )
+ {
+ img = info->m_img + i * info->m_rowbytes;
+
+ for ( j = 0; j < info->m_width; j++ )
+ {
+ k = j + 2;
+ sc1 = * ( rowa + k );
+ sc2 = * ( rowb + k - 1 );
+ sc3 = * ( rowb + k );
+ sc4 = * ( rowb + k + 1 );
+ sc5 = * ( rowc + k - 2 );
+ sc6 = * ( rowc + k - 1 );
+
+ val = dropout ( * img, * ( img + 1 ), * ( img + 2 ),
+ do_clr, thdiff );
+ long_val = val + ( sc3 + sc6 ) / 4 + ( sc1 + sc2 + sc4 + sc5 ) / 8;
+ thre = * ( pattern + ( i & ( matrix_size - 1 ) ) * matrix_size +
+ ( j & ( matrix_size - 1 ) ) );
+ if ( long_val >= thre )
+ {
+ val = 255;
+ * ( rowc + k ) = long_val - 255;
+ }
+ else
+ {
+ val = 0;
+ * ( rowc + k ) = long_val;
+ }
+
+ * ( img + 0 ) = val;
+ * ( img + 1 ) = val;
+ * ( img + 2 ) = val;
+ img += 3;
+ }
+ ::memcpy ( rowa, rowb, ( info->m_width + 10 ) * sizeof ( long ) );
+ ::memcpy ( rowb, rowc, ( info->m_width + 10 ) * sizeof ( long ) );
+ ::memset ( rowc, 0, ( info->m_width + 10 ) * sizeof ( long ) );
+ }
+
+ delete [ ] rowa;
+ delete [ ] rowb;
+ delete [ ] rowc;
+
+ return PISA_ERR_SUCCESS;
+}
+
+
+/*------------------------------------------------------------*/
+static int build_dither ( pisa_image_info * info,
+ unsigned char do_clr,
+ unsigned char halftone,
+ long threshold )
+{
+ int i, j;
+ unsigned char val;
+ unsigned char * img;
+ long thdiff, thre;
+ long * pattern;
+ long matrix_size;
+
+ thdiff = 128 - threshold;
+
+ pattern = proc_halftone [ halftone ].matrix;
+ if ( pattern == 0 )
+ return PISA_ERR_PARAMETER;
+
+ matrix_size = proc_halftone [ halftone ].size;
+
+ for ( i = 0; i < info->m_height; i++ )
+ {
+ img = info->m_img + i * info->m_rowbytes;
+ for ( j = 0; j < info->m_width; j++ )
+ {
+ val = dropout ( * img, * ( img + 1 ), * ( img + 2 ),
+ do_clr, thdiff );
+ thre = * ( pattern + ( i & ( matrix_size - 1 ) ) * matrix_size +
+ ( j & ( matrix_size - 1 ) ) );
+ if ( ( long ) val >= thre )
+ val = 255;
+ else
+ val = 0;
+ * ( img + 0 ) = val;
+ * ( img + 1 ) = val;
+ * ( img + 2 ) = val;
+ }
+ }
+
+ return PISA_ERR_SUCCESS;
+}
+
+/*------------------------------------------------------------*/
+static unsigned char dropout ( unsigned char red,
+ unsigned char grn,
+ unsigned char blu,
+ unsigned char do_clr,
+ long thdiff )
+{
+ unsigned char ret;
+ long val;
+
+ ret = 0;
+
+ if ( thdiff != 0 )
+ {
+ // red
+ val = red + thdiff;
+ if ( val < 0 ) val = 0;
+ if ( val > 255 ) val = 255;
+ red = val;
+
+ // green
+ val = grn + thdiff;
+ if ( val < 0 ) val = 0;
+ if ( val > 255 ) val = 255;
+ grn = val;
+
+ // blue
+ val = blu + thdiff;
+ if ( val < 0 ) val = 0;
+ if ( val > 255 ) val = 255;
+ blu = val;
+ }
+
+ switch ( do_clr )
+ {
+ case PISA_DO_NONE:
+ val = red * 2 + grn * 6 + blu * 2;
+ if ( val == 0 )
+ ret = 0;
+ else
+ ret = ( unsigned char ) ( val / 10 );
+ break;
+
+ case PISA_DO_RED:
+ ret = red;
+ break;
+
+ case PISA_DO_GREEN:
+ ret = grn;
+ break;
+
+ case PISA_DO_BLUE:
+ ret = blu;
+ break;
+ }
+
+ return ret;
+}
diff --git a/frontend/pisa_scan_tool.h b/frontend/pisa_scan_tool.h
new file mode 100644
index 0000000..eeb86e9
--- /dev/null
+++ b/frontend/pisa_scan_tool.h
@@ -0,0 +1,63 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001, 2005, 2008 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___EPS_LIVE_PREVIEW_H
+#define ___EPS_LIVE_PREVIEW_H
+
+#include "pisa_structs.h"
+#include "pisa_enums.h"
+
+/*------------------------------------------------------------*/
+/* FIXME: calc_*() only used by scan_manager --> make member function
+ */
+int calc_rowbytes ( int width, pisa_pixel_type type );
+int calc_bitperpix ( pisa_pixel_type pixel, pisa_bitdepth_type bitdepth );
+
+int tool_lut ( pisa_image_info * info,
+ const gamma_struct * lut );
+int tool_matrix ( pisa_image_info * info,
+ int saturation,
+ const double * color_profile );
+
+int generate_color_coef ( double * dst,
+ const double * src,
+ int saturation );
+
+int build_gray ( pisa_image_info * info,
+ unsigned char do_clr );
+
+int build_bw ( pisa_image_info * info,
+ unsigned char do_clr,
+ unsigned char halftone,
+ long threshold );
+
+#endif // ___EPS_LIVE_PREVIEW_H
+
+
diff --git a/frontend/pisa_settings.cc b/frontend/pisa_settings.cc
new file mode 100644
index 0000000..fada091
--- /dev/null
+++ b/frontend/pisa_settings.cc
@@ -0,0 +1,149 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001, 2008 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+/*------------------------------------------------------------*/
+#include <stdio.h>
+
+/*------------------------------------------------------------*/
+#include "pisa_structs.h"
+#include "pisa_settings.h"
+
+/*------------------------------------------------------------*/
+void settings::init ( marquee ** marq )
+{
+ marquee_node * new_node;
+
+ new_node = new marquee_node;
+
+ new_node->next = 0;
+ new_node->data = * marq;
+
+ top = new_node;
+}
+
+long
+settings::get_marquee_size (void) const
+{
+ long size;
+ marquee_node * tmp;
+
+ size = 1;
+ tmp = top;
+
+ while ( tmp->next )
+ {
+ tmp = tmp->next;
+ size++;
+ }
+
+ return size;
+}
+
+const marquee&
+settings::get_marquee (long index) const
+{
+ return const_cast <settings *> (this)->get_marquee (index);
+}
+
+marquee&
+settings::get_marquee (long index)
+{
+ if (0 > index)
+ index = get_marquee_size () - 1;
+
+ marquee_node *current = top;
+ long counter = 0;
+
+ while (index != counter++)
+ current = current->next;
+
+ return *current->data;
+}
+
+/*------------------------------------------------------------*/
+void settings::add_marquee ( marquee ** marq )
+{
+ marquee_node * tmp;
+ marquee_node * new_node;
+
+ new_node = new marquee_node;
+
+ tmp = top;
+
+ while ( tmp->next )
+ tmp = tmp->next;
+
+ new_node->next = 0;
+ new_node->data = * marq;
+
+ tmp->next = new_node;
+}
+
+
+void
+settings::del_marquee (long index)
+{
+ if (!top) return;
+
+ if (0 > index)
+ index = get_marquee_size () - 1;
+
+ int cnt = 0;
+ marquee_node *del_node = top;
+ marquee_node *pre_node = top;
+
+ while (cnt != index - 1)
+ {
+ if (pre_node->next)
+ {
+ pre_node = pre_node->next;
+ ++index;
+ }
+ else
+ return; // error
+ }
+
+ del_node = pre_node->next;
+ pre_node->next = del_node->next;
+
+ delete del_node->data;
+ delete del_node;
+}
+
+/*------------------------------------------------------------*/
+void settings::delete_all ( void )
+{
+ while ( top )
+ {
+ delete top->data;
+ top = top->next;
+ }
+}
+
diff --git a/frontend/pisa_settings.h b/frontend/pisa_settings.h
new file mode 100644
index 0000000..f7554fa
--- /dev/null
+++ b/frontend/pisa_settings.h
@@ -0,0 +1,75 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001, 2008 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_SETTINGS_H
+#define ___PISA_SETTINGS_H
+
+#include "pisa_structs.h"
+#include "pisa_marquee.h"
+
+class settings
+{
+ public:
+
+ // attribute
+ char destination;
+
+ imagetype imgtype;
+ long resolution;
+ bool enable_start_button;
+ bool enable_draft_mode;
+ long usm;
+
+ double max_area [ 2 ];
+ double coef [ 9 ];
+
+ long unit;
+
+ // operation
+ void init ( marquee ** marq );
+
+ long get_marquee_size (void) const;
+
+ const marquee& get_marquee (long index = -1) const;
+ marquee& get_marquee (long index = -1);
+
+ void add_marquee ( marquee ** marq );
+ void del_marquee (long index = -1);
+ void delete_all ( void );
+
+ private:
+
+ // attribute
+ marquee_node * top;
+
+};
+
+#endif // ___PISA_SETTINGS_H
+
diff --git a/frontend/pisa_structs.h b/frontend/pisa_structs.h
new file mode 100644
index 0000000..29b5a61
--- /dev/null
+++ b/frontend/pisa_structs.h
@@ -0,0 +1,226 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001, 2005, 2008 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_STRUCTS_H
+#define ___PISA_STRUCTS_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_GTK_GTK_H
+#include <gtk/gtk.h>
+#ifndef HAVE_GTK_2
+#include <gdk_imlib.h>
+#endif
+#endif
+
+// scanner
+/*--------------------------------------------------------------*/
+typedef struct _gamma_struct
+{
+ unsigned char gamma_r [ 256 ];
+ unsigned char gamma_g [ 256 ];
+ unsigned char gamma_b [ 256 ];
+} gamma_struct;
+
+
+/*--------------------------------------------------------------*/
+typedef struct _imagetype
+{
+ long id;
+ char pixeltype;
+ char bitdepth;
+ char de_screening;
+ char ae_option;
+ char dropout;
+ char monoopt;
+ char halftone;
+} imagetype;
+
+// ui
+/*--------------------------------------------------------------*/
+template <class Type>
+struct _point
+{
+ Type x, y;
+
+ _point ( ) { x = y = 0; }
+ _point ( Type u, Type v = 0 ) { x = u, y = v; }
+ _point ( const _point & pt ) { x = pt.x; y = pt.y; }
+ _point & operator= ( const _point & pt )
+ {
+ if ( & pt != this )
+ {
+ x = pt.x;
+ y = pt.y;
+ }
+ return ( * this );
+ }
+
+ // == operator
+ friend int operator== ( const _point & pt1, const _point & pt2 )
+ {
+ return ( pt1.x == pt2.x && pt1.y == pt2.y );
+ }
+
+ // - operator
+ friend _point operator- ( const _point & pt1, const _point & pt2 )
+ {
+ return ( _point ( pt1.x - pt2.x, pt1.y - pt2.y ) );
+ }
+
+ // + operator
+ friend _point operator+ ( const _point & pt1, const _point & pt2 )
+ {
+ return ( _point ( pt1.x + pt2.x, pt1.y + pt2.y ) );
+ }
+};
+
+typedef _point <double> _pointD;
+typedef _point <long> _pointL;
+
+/*--------------------------------------------------------------*/
+template <class Type>
+struct _rect
+{
+ Type top, bottom, right, left;
+
+ _rect ( ) { top = bottom = right = left = Type ( 0 ); }
+ _rect ( Type _top, Type _bottom, Type _right, Type _left ):
+ top ( _top ), bottom ( _bottom ), right ( _right ), left ( _left ) { }
+ _rect ( const _rect & x )
+ {
+ top = x.top, bottom = x.bottom;
+ right = x.right, left = x.left;
+ }
+ _rect & operator= ( const _rect & x )
+ {
+ if ( & x != this )
+ {
+ top = x.top, bottom = x.bottom;
+ right = x.right, left = x.left;
+ }
+ return ( * this );
+ }
+
+ // == operator
+ friend int operator== ( const _rect & rc1, const _rect & rc2 )
+ {
+ return ( rc1.top == rc2.top && rc1.bottom == rc2.bottom &&
+ rc1.right == rc2.right && rc1.left == rc2.left );
+ }
+
+ // - operator
+ friend _rect operator- ( const _rect & rc1, const _rect & rc2 )
+ {
+ return ( _rect ( rc1.top - rc2.top, rc1.bottom - rc2.bottom,
+ rc1.right - rc2.right, rc1.left - rc2.left ) );
+ }
+
+ // + operator
+ friend _rect operator+ ( const _rect & rc1, const _rect & rc2 )
+ {
+ return ( _rect ( rc1.top + rc2.top, rc1.bottom + rc2.bottom,
+ rc1.right + rc2.right, rc1.left + rc2.left ) );
+ }
+
+ // empty?
+ friend int is_rect_empty ( const _rect & rc )
+ {
+ return ( rc.top == 0 && rc.bottom == 0 && rc.right == 0 && rc.left == 0 );
+ }
+};
+
+typedef _rect <_point <long> > _rectPL;
+typedef _rect <double> _rectD;
+typedef _rect <long> _rectL;
+
+
+#ifdef HAVE_GTK_GTK_H
+/*--------------------------------------------------------------*/
+typedef struct _toolbar_items
+{
+ GtkWidget * widget;
+ char ** xpm;
+ GtkSignalFunc * func;
+} toolbar_items;
+
+/*--------------------------------------------------------------*/
+typedef struct _menu_items
+{
+ long id;
+ GtkWidget * widget;
+ const char * name;
+ GtkSignalFunc * func;
+} menu_items;
+
+
+/*------------------------------------------------------------*/
+typedef struct _scale_items
+{
+ long id;
+ GtkWidget * widget;
+ GtkAdjustment * adjust;
+ GtkSignalFunc * func;
+ float val;
+ float min;
+ float max;
+ float step;
+ float page;
+} scale_items;
+#endif
+
+typedef struct
+{
+ unsigned char * m_img;
+ long m_width;
+ long m_height;
+ long m_rowbytes;
+} _pisa_image_info;
+
+#include <cstddef>
+
+class pisa_image_info : public _pisa_image_info
+{
+public:
+ size_t m_bits_per_pixel;
+
+ pisa_image_info () : m_bits_per_pixel (24)
+ {
+ m_img = 0;
+ m_width = 0;
+ m_height = 0;
+ m_rowbytes = 0;
+ };
+};
+
+#endif // ___PISA_STRUCTS_H
+
diff --git a/frontend/pisa_tool.cc b/frontend/pisa_tool.cc
new file mode 100644
index 0000000..34864db
--- /dev/null
+++ b/frontend/pisa_tool.cc
@@ -0,0 +1,184 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+/*--------------------------------------------------------------*/
+#include "pisa_tool.h"
+
+#include "gettext.h"
+#define _(msg_id) gettext (msg_id)
+
+template <>
+double similarity <double> ( const double & A,
+ const double & a,
+ const double & b )
+{
+ double B;
+
+ if ( a == 0 )
+ return 0;
+
+ B = ( b * A ) / a;
+
+ return B;
+}
+
+/*--------------------------------------------------------------*/
+GtkWidget * xpm2widget ( GtkWidget * widget, char ** xpm_data )
+{
+ GdkBitmap * mask;
+ GdkPixmap * pixmap;
+ GtkWidget * pixmap_widget;
+ GtkStyle * style;
+
+ style = ::gtk_widget_get_style ( widget );
+
+ pixmap = ::gdk_pixmap_create_from_xpm_d ( widget->window,
+ & mask,
+ & style->bg [ GTK_STATE_NORMAL ],
+ xpm_data );
+
+ pixmap_widget = ::gtk_pixmap_new ( pixmap, mask );
+
+ ::gtk_widget_show ( pixmap_widget );
+
+ return pixmap_widget;
+}
+
+
+/*------------------------------------------------------------*/
+GtkWidget * xpmlabelbox ( GtkWidget * parent, char ** xpm_data,
+ const char * text )
+{
+ GtkWidget * hbox;
+ GtkWidget * label;
+ GtkWidget * img;
+
+ hbox = ::gtk_hbox_new ( FALSE, 0 );
+ ::gtk_container_border_width ( GTK_CONTAINER ( hbox ), 3 );
+
+ img = ::xpm2widget ( parent, xpm_data );
+ ::gtk_box_pack_start ( GTK_BOX ( hbox ), img, FALSE, FALSE, 3 );
+ ::gtk_widget_show ( img );
+
+ label = ::gtk_label_new ( text );
+ ::gtk_box_pack_start ( GTK_BOX ( hbox ), label, FALSE, FALSE, 3 );
+ ::gtk_widget_show ( label );
+
+ return hbox;
+}
+
+
+/*------------------------------------------------------------*/
+GtkWidget * pisa_create_toolbar ( const GtkWidget * parent,
+ toolbar_items * list )
+{
+ GtkWidget * toolbar;
+
+#ifdef HAVE_GTK_2
+ toolbar = ::gtk_toolbar_new ();
+#else
+ toolbar = ::gtk_toolbar_new ( GTK_ORIENTATION_HORIZONTAL,
+ GTK_TOOLBAR_ICONS );
+#endif
+
+ while ( list->func )
+ {
+ if ( list->xpm )
+ {
+ list->widget = ::xpm2widget ( GTK_WIDGET ( parent ), list->xpm );
+ ::gtk_toolbar_append_item ( GTK_TOOLBAR ( toolbar ),
+ 0, 0, 0,
+ list->widget,
+ GTK_SIGNAL_FUNC ( list->func ),
+ 0 );
+ }
+ else
+ ::gtk_toolbar_append_space ( GTK_TOOLBAR ( toolbar ) );
+
+ list++;
+ }
+
+ return toolbar;
+}
+
+
+/*------------------------------------------------------------*/
+GtkWidget * pisa_create_option_menu ( menu_items * list )
+{
+ GtkWidget * option_menu;
+ GtkWidget * menu, * item;
+
+ option_menu = ::gtk_option_menu_new ( );
+ ::gtk_widget_show ( option_menu );
+
+ menu = ::gtk_menu_new ( );
+
+ while ( list->func )
+ {
+ list->widget = item = ::gtk_menu_item_new_with_label (_(list->name));
+ ::gtk_signal_connect ( GTK_OBJECT ( item ), "activate",
+ GTK_SIGNAL_FUNC ( list->func ),
+ ( void * ) & list->id );
+ ::gtk_menu_append ( GTK_MENU ( menu ), item );
+ ::gtk_widget_show ( item );
+
+ list++;
+ }
+
+ ::gtk_option_menu_set_menu ( GTK_OPTION_MENU ( option_menu ), menu );
+
+ return option_menu;
+}
+
+
+/*--------------------------------------------------------------*/
+GtkWidget * pisa_create_scale ( scale_items * list )
+{
+ GtkWidget * scale;
+ GtkObject * adjust;
+
+ adjust = ::gtk_adjustment_new ( list->val,
+ list->min,
+ list->max,
+ list->step,
+ list->page,
+ 0 );
+ list->adjust = GTK_ADJUSTMENT ( adjust );
+
+ ::gtk_signal_connect ( adjust, "value_changed",
+ GTK_SIGNAL_FUNC ( list->func ),
+ ( void * ) & list->id );
+
+ scale = ::gtk_hscale_new ( GTK_ADJUSTMENT ( adjust ) );
+
+ list->widget = scale;
+
+ return scale;
+}
diff --git a/frontend/pisa_tool.h b/frontend/pisa_tool.h
new file mode 100644
index 0000000..e4edae3
--- /dev/null
+++ b/frontend/pisa_tool.h
@@ -0,0 +1,79 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_TOOL_H
+#define ___PISA_TOOL_H
+
+#include <gtk/gtk.h>
+#include <config.h>
+#include "pisa_structs.h"
+
+template <class Type>
+Type similarity ( const Type & A,
+ const Type & a,
+ const Type & b )
+{
+ Type B;
+
+ if ( a == 0 )
+ return 0;
+
+ if ( 0 < A )
+ B = ( b * A + a / 2 ) / a;
+ else
+ B = ( b * A - a / 2 ) / a;
+
+ return B;
+}
+
+
+template <class Type>
+int pt_in_rect ( const _rect <Type> & rect, const _point <Type> & pt )
+{
+ if ( rect.left < pt.x && pt.x < rect.right &&
+ rect.top < pt.y && pt.y < rect.bottom )
+ return 1;
+ else
+ return 0;
+}
+
+GtkWidget * xpm2widget ( GtkWidget * widget, char ** xpm_data );
+GtkWidget * xpmlabelbox ( GtkWidget * parent, char ** xpm_data,
+ const char * text );
+
+GtkWidget * pisa_create_toolbar ( const GtkWidget * parent,
+ toolbar_items * list );
+GtkWidget * pisa_create_option_menu ( menu_items * list );
+GtkWidget * pisa_create_scale ( scale_items * items );
+
+
+
+#endif // ___PISA_TOOL_H
+
diff --git a/frontend/pisa_view_manager.cc b/frontend/pisa_view_manager.cc
new file mode 100644
index 0000000..ef7a966
--- /dev/null
+++ b/frontend/pisa_view_manager.cc
@@ -0,0 +1,1134 @@
+/* pisa_view_manager.cc
+ Copyright (C) 2001, 2004, 2005, 2008, 2009 SEIKO EPSON CORPORATION
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#include <config.h>
+
+#include "gettext.h"
+#define _(msg_id) gettext (msg_id)
+
+/*------------------------------------------------------------*/
+#include <gtk/gtk.h>
+#ifndef HAVE_GTK_2
+#include <gdk_imlib.h>
+#endif
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <regex.h>
+#include <dirent.h>
+#include <locale.h>
+#include <libgen.h>
+#include <time.h>
+
+/*------------------------------------------------------------*/
+#include "pisa_view_manager.h"
+#include "pisa_error.h"
+#include "pisa_main.h"
+#include "pisa_scan_tool.h"
+#include "pisa_default_val.h"
+#include "pisa_gimp.h"
+#include "pisa_aleart_dialog.h"
+#include "pisa_change_unit.h"
+#include "pisa_preference.h"
+#include "pisa_scan_selector.h"
+
+#include "file-selector.h"
+
+#include "imgstream.hh"
+
+
+#define DEFAULT_RESOLUTION 300 // dpi
+
+/*------------------------------------------------------------*/
+view_manager * g_view_manager = 0;
+
+/*------------------------------------------------------------*/
+int view_manager::create_view_manager ( int argc, char * argv [ ] )
+{
+ ::gtk_set_locale ( );
+ ::setlocale (LC_NUMERIC, "C");
+ ::gtk_init ( & argc, & argv );
+
+#ifndef HAVE_GTK_2
+ ::gdk_imlib_init ( );
+ ::gtk_widget_push_visual ( ::gdk_imlib_get_visual ( ) );
+ ::gtk_widget_push_colormap ( ::gdk_imlib_get_colormap ( ) );
+#endif
+
+ try
+ {
+ if ( ::g_view_manager )
+ throw pisa_error ( PISA_ERR_PARAMETER );
+
+ ::g_view_manager = new view_manager;
+ if ( ::g_view_manager == 0 )
+ throw pisa_error ( PISA_ERR_OUTOFMEMORY );
+
+ // initialize parameters
+ ::g_view_manager->init ( );
+
+ }
+ catch ( pisa_error & err )
+ {
+ aleart_dialog aleart_dlg;
+
+ aleart_dlg.message_box ( 0, err.get_error_string ( ) );
+
+ if ( ::g_view_manager )
+ {
+ delete ::g_view_manager;
+ ::g_view_manager = 0;
+ }
+
+ return err.get_error_id ( );
+ }
+
+ return PISA_ERR_SUCCESS;
+}
+
+/*------------------------------------------------------------*/
+void view_manager::release_view_manager ( void )
+{
+ if ( ::g_view_manager )
+ {
+ delete ::g_view_manager;
+ ::g_view_manager = 0;
+ }
+}
+
+/*------------------------------------------------------------*/
+int view_manager::main ( void )
+{
+ gtk_main ( );
+
+ return PISA_ERR_SUCCESS;
+}
+
+/*------------------------------------------------------------*/
+void view_manager::init ( void )
+{
+ // initialize
+ m_scanmanager_cls = 0;
+ m_main_cls = 0;
+ m_prev_cls = 0;
+ m_imgctrl_cls = 0;
+ m_gamma_cls = 0;
+ m_config_cls = 0;
+ m_filsel_cls = 0;
+ m_scansel_cls = 0;
+
+ // open scanner
+ m_scanmanager_cls = new scan_manager;
+
+ if ( ! m_scanmanager_cls )
+ throw pisa_error ( PISA_ERR_OUTOFMEMORY );
+
+ open_device ( );
+
+ m_set.resolution = DEFAULT_RESOLUTION;
+ m_set.enable_start_button =
+ m_scanmanager_cls->push_button_needs_polling ();
+ m_set.enable_draft_mode = false;
+ m_set.usm = 1;
+ m_set.unit = PISA_UNIT_INCHES;
+
+ if ( pisa_gimp_plugin ( ) )
+ m_set.destination = PISA_DE_GIMP;
+ else
+ m_set.destination = PISA_DE_FILE;
+
+ m_main_cls = new main_window;
+ m_prev_cls = new preview_window;
+ m_imgctrl_cls = new image_controls;
+ m_gamma_cls = new gamma_correction;
+ m_config_cls = new config_window;
+ m_filsel_cls = new file_selector;
+
+ if ( ! m_main_cls ||
+ ! m_prev_cls ||
+ ! m_imgctrl_cls ||
+ ! m_gamma_cls ||
+ ! m_config_cls ||
+ ! m_filsel_cls )
+ {
+ destroy ( );
+ throw ( PISA_ERR_OUTOFMEMORY );
+ }
+
+ init_img_info ();
+
+ m_main_cls->init ( );
+ m_prev_cls->init ( );
+ m_gamma_cls->init ( );
+ m_config_cls->init ( );
+ m_filsel_cls->init ( );
+
+ load_preference ( );
+
+}
+
+/*------------------------------------------------------------*/
+void view_manager::destroy ( void )
+{
+ save_preference ( );
+
+ m_set.delete_all ( );
+
+ if ( m_scanmanager_cls )
+ {
+ try
+ {
+ close_device ( );
+ }
+ catch ( pisa_error & err )
+ {
+ aleart_dialog aleart_dlg;
+
+ aleart_dlg.message_box ( 0, err.get_error_string ( ) );
+ }
+
+ delete m_scanmanager_cls;
+ m_scanmanager_cls = 0;
+ }
+
+ if ( m_main_cls )
+ {
+ delete m_main_cls;
+ m_main_cls = 0;
+ }
+
+ if ( m_prev_cls )
+ {
+ delete m_prev_cls;
+ m_prev_cls = 0;
+ }
+
+ if ( m_imgctrl_cls )
+ {
+ delete m_imgctrl_cls;
+ m_imgctrl_cls = 0;
+ }
+
+ if ( m_gamma_cls )
+ {
+ delete m_gamma_cls;
+ m_gamma_cls = 0;
+ }
+
+ if ( m_config_cls )
+ {
+ delete m_config_cls;
+ m_config_cls = 0;
+ }
+
+ if ( m_filsel_cls )
+ {
+ delete m_filsel_cls;
+ m_filsel_cls = 0;
+ }
+
+ if ( m_scansel_cls )
+ {
+ delete m_scansel_cls;
+ m_scansel_cls = 0;
+ }
+
+ ::pisa_quit ( );
+}
+
+/*------------------------------------------------------------*/
+GtkWidget * view_manager::create_window ( pisa_window_id id )
+{
+ GtkWidget * ret = 0;
+
+ switch ( id )
+ {
+ case ID_WINDOW_MAIN:
+ ret = m_main_cls->create_window ( 0 );
+ break;
+
+ case ID_WINDOW_PREV:
+ ret = m_prev_cls->create_window ( 0 );
+ break;
+
+ case ID_WINDOW_CONFIG:
+ ret = m_config_cls->create_window ( m_main_cls->get_widget ( ) );
+ break;
+ }
+
+ return ret;
+}
+
+/*------------------------------------------------------------*/
+int view_manager::close_window ( pisa_window_id id, int destroy_flag )
+{
+ switch ( id )
+ {
+ case ID_WINDOW_MAIN:
+ m_main_cls->close_window ( destroy_flag );
+ destroy ( );
+ break;
+
+ case ID_WINDOW_PREV:
+ m_prev_cls->close_window ( destroy_flag );
+ break;
+
+ case ID_WINDOW_CONFIG:
+ m_config_cls->close_window ( destroy_flag );
+ break;
+
+ default:
+ return PISA_ERR_PARAMETER;
+ }
+
+ return PISA_ERR_PARAMETER;
+}
+
+image_controls *
+view_manager::get_image_controls ()
+{
+ return m_imgctrl_cls;
+}
+
+gamma_correction *
+view_manager::get_gamma_correction ()
+{
+ return m_gamma_cls;
+}
+
+/*------------------------------------------------------------*/
+void * view_manager::get_window_cls ( pisa_window_id id )
+{
+ void * ret = 0;
+
+ switch ( id )
+ {
+ case ID_WINDOW_MAIN:
+ ret = m_main_cls;
+ break;
+
+ case ID_WINDOW_PREV:
+ ret = m_prev_cls;
+ break;
+
+ case ID_WINDOW_CONFIG:
+ ret = m_config_cls;
+ break;
+ }
+
+ return ret;
+}
+
+/*------------------------------------------------------------*/
+void view_manager::sensitive ( void )
+{
+ int is_img = m_prev_cls->is_prev_img ( );
+
+ m_main_cls->sensitive ( is_img );
+ m_imgctrl_cls->sensitive ( is_img );
+ m_gamma_cls->sensitive ( is_img );
+
+ m_scanmanager_cls->has_prev_img ( is_img );
+}
+
+/*------------------------------------------------------------*/
+int view_manager::is_gimp ( void )
+{
+ return pisa_gimp_plugin ( );
+}
+
+bool
+view_manager::needs_duplex_rotation (void) const
+{
+ if (!m_scanmanager_cls->using_duplex ())
+ return false;
+
+ if (!m_scanmanager_cls->adf_duplex_direction_matches ())
+ { // double pass ADF
+ return m_filsel_cls->has_left_edge_binding ();
+ }
+ else
+ { // single pass ADF
+ return !m_filsel_cls->has_left_edge_binding ();
+ }
+}
+
+/*------------------------------------------------------------*/
+void view_manager::start_scan ( void )
+{
+ using iscan::file_opener;
+ using iscan::imgstream;
+
+ if (PISA_ERR_SUCCESS != init_scan_param ())
+ return;
+
+ file_opener *fo = NULL;
+ imgstream *is = NULL;
+
+ bool remove = false;
+
+ // All types of scans need to provide visual feedback on their
+ // progress and for consecutive scans (via ADF or with the start
+ // button enabled) it is better to create it here. That way, it
+ // will not disappear and reappear between calls to scan_file() or
+ // scan_gimp(), eliminating rather annoying flicker.
+ _feedback = new progress_window (m_main_cls->get_widget ());
+ switch ( m_set.destination )
+ {
+ case PISA_DE_FILE:
+ {
+ m_filsel_cls->init ();
+#ifndef HAVE_GTK_2
+ m_filsel_cls->create_window (m_main_cls->get_widget (), m_set.option,
+ m_set.enable_start_button,
+ PISA_PT_BW == m_set.imgtype.pixeltype);
+#else
+ GtkWidget *w = m_main_cls->get_widget ();
+ m_filsel_cls->create_window (GTK_WINDOW (gtk_widget_get_parent (w)), w,
+ (m_scanmanager_cls->using_adf ()
+ || m_set.enable_start_button),
+ m_scanmanager_cls->using_duplex (),
+ _image_format.c_str ());
+#endif /* HAVE_GTK_2 */
+ m_filsel_cls->hide ();
+ if (!m_filsel_cls->get_pathname ()) // dialog cancelled
+ break;
+
+ // FIXME: the file_selector (or file name generator) should
+ // signal a change in file format and all interested
+ // parties, e.g. an object managing the configuration
+ // file, should take note and appropriate action
+ _image_format = m_filsel_cls->get_format_name ();
+
+ if (is_multi_image ()
+ && !m_filsel_cls->multi_page_mode ())
+ fo = new file_opener (std::string (m_filsel_cls->get_pathname ()),
+ m_filsel_cls->get_sequence_number ());
+ else
+ fo = new file_opener (std::string (m_filsel_cls->get_pathname ()));
+
+ try
+ {
+ try
+ {
+ is = iscan::create_imgstream (*fo, m_filsel_cls->get_type (),
+ needs_duplex_rotation ());
+ }
+ catch (std::exception& oops)
+ {
+ throw pisa_error ( PISA_ERR_OUTOFMEMORY );
+ }
+ }
+ catch (pisa_error& err)
+ {
+ aleart_dialog aleart_dlg;
+ aleart_dlg.message_box (m_main_cls->get_widget (),
+ err.get_error_string ());
+ m_filsel_cls->destroy ();
+ remove = true;
+ break;
+ }
+ remove = do_scan (*is, *fo);
+ m_filsel_cls->destroy ();
+ }
+ break;
+
+ case PISA_DE_PRINTER:
+ {
+ fo = new file_opener (false);
+ is = new imgstream (*fo, iscan::PNG);
+ remove = do_scan (*is, *fo);
+ }
+ break;
+
+ case PISA_DE_GIMP:
+ do_scan_gimp ();
+ break;
+ }
+ delete is;
+ try
+ {
+ if (fo && remove) fo->remove ();
+ }
+ catch (std::exception& oops) {}
+ delete fo;
+ delete _feedback;
+
+ m_scanmanager_cls->finish_acquire ();
+}
+
+int
+view_manager::update_lut (long index)
+{
+ return update_lut (m_set.get_marquee (index));
+}
+
+int
+view_manager::update_lut (marquee& m)
+{
+ iscan::build_LUT (m_scanmanager_cls->get_scan_source (),
+ m_scanmanager_cls->get_film_type (),
+ m_set, m, !m_scanmanager_cls->has_zoom ());
+
+ return PISA_ERR_SUCCESS;
+}
+
+bool
+view_manager::change_document_source (const imagetype *img_type)
+{
+ bool success = true;
+
+ try
+ {
+ set_image_type (img_type);
+ m_set.delete_all ();
+ init_img_info (); // calls m_set.init()
+
+ main_window *main_cls
+ = static_cast <main_window *> (get_window_cls (ID_WINDOW_MAIN));
+ main_cls->enable_start_button (!m_scanmanager_cls->using_adf ());
+ }
+ catch (pisa_error& err)
+ {
+ aleart_dialog aleart_dlg;
+
+ aleart_dlg.message_box (m_main_cls->get_widget (),
+ err.get_error_string ());
+ success = false;
+ }
+
+ set_resolution (DEFAULT_RESOLUTION);
+ m_gamma_cls->reset (1);
+ m_prev_cls->resize_window ();
+ sensitive ();
+
+ return success;
+}
+
+void view_manager::set_device( char * name )
+{
+ m_scanmanager_cls->close_device();
+ m_scanmanager_cls->open_device( name );
+}
+
+char * view_manager::get_device_name() const
+{
+ return m_scansel_cls->get_device( true );
+}
+
+/*------------------------------------------------------------*/
+void view_manager::open_device ( void )
+{
+ sane_init( 0, 0 );
+ if (!m_scansel_cls)
+ m_scansel_cls = new scan_selector( true ); // dialog box
+ m_scanmanager_cls->open_device ( m_scansel_cls->get_device() );
+}
+
+/*------------------------------------------------------------*/
+void view_manager::close_device ( void )
+{
+ m_scanmanager_cls->close_device ( );
+}
+
+/*------------------------------------------------------------*/
+void view_manager::load_preference ( void )
+{
+ char pref_path [ 256 ];
+ char pips_path [ 1024 ] = "lpr";
+ char image_format [ 1024 ] = "PNG";
+
+ cfg_struct cfg [ ] =
+ {
+ { "IMG", CFG_STRING, image_format },
+ { "PIPS", CFG_STRING, pips_path }
+ };
+
+ ::strcpy ( pref_path, ::getenv ( "HOME" ) );
+ ::strcat ( pref_path, "/" );
+ ::strcat ( pref_path, PREFERENCE );
+
+ ::get_cfg ( pref_path, cfg, sizeof ( cfg ) / sizeof ( cfg [ 0 ] ) );
+
+ ::strcpy ( m_config_cls->m_cmd, pips_path );
+ _image_format = image_format;
+}
+
+/*------------------------------------------------------------*/
+void view_manager::save_preference ( void )
+{
+ char pref_path [ 256 ];
+ char pips_path [ 1024 ] = "";
+
+ cfg_struct cfg [ ] =
+ {
+ { "IMG", CFG_STRING, const_cast<char*> (_image_format.c_str ()) },
+ { "PIPS", CFG_STRING, pips_path }
+ };
+
+ ::strcpy ( pref_path, ::getenv ( "HOME" ) );
+ ::strcat ( pref_path, "/" );
+ ::strcat ( pref_path, PREFERENCE );
+
+ ::strcpy ( pips_path, m_config_cls->m_cmd );
+
+ ::set_cfg ( pref_path, cfg, sizeof ( cfg ) / sizeof ( cfg [ 0 ] ) );
+}
+
+
+int
+view_manager::init_img_info (void)
+{
+ int i;
+ marquee * marq;
+ double max_width, max_height;
+ double coef [ 9 ];
+ float brightness, contrast;
+ long focus;
+
+
+ m_scanmanager_cls->set_brightness_method(br_iscan);
+ m_scanmanager_cls->get_value(SANE_NAME_BRIGHTNESS, &brightness);
+ m_scanmanager_cls->get_value(SANE_NAME_CONTRAST, &contrast);
+
+ if (m_scanmanager_cls->using_tpu ())
+ focus = 25;
+ else
+ focus = 0;
+
+ // get max area
+ m_scanmanager_cls->get_current_max_size ( & max_width, & max_height );
+ m_scanmanager_cls->get_color_profile ( coef );
+
+ // initialize marquee
+ marq = new marquee ( max_width, max_height, focus, brightness, contrast );
+ m_set.init ( & marq );
+
+ m_set.max_area [ 0 ] = max_width;
+ m_set.max_area [ 1 ] = max_height;
+
+ for ( i = 0; i < 9; i++ )
+ m_set.coef [ i ] = coef [ i ];
+
+ return PISA_ERR_SUCCESS;
+}
+
+int
+view_manager::init_scan_param (void)
+{
+ marquee marq = get_marquee (m_set.get_marquee_size ( ) - 1);
+
+ pisa_error_id err = m_scanmanager_cls->set_scan_parameters (m_set, marq);
+
+ if (PISA_ERR_SUCCESS != err)
+ {
+ aleart_dialog dlg;
+
+ dlg.message_box (m_main_cls->get_widget (),
+ pisa_error (err).get_error_string ());
+ }
+ return err;
+}
+
+// A pile of status flags used to track what's going on in the scan
+// loop. There are probably a few more than really needed, but at
+// least these cover the individual situations we can encounter.
+
+#define SCAN_BUTTON 0x10
+#define SCAN_ADF 0x20
+#define SCAN_NEXT 0x40
+#define SCAN_FINISH 0x80
+#define SCAN_ERROR 0x01
+#define SCAN_CANCEL 0x02
+#define SCAN_SINGLE 0x04
+#define SCAN_DATA 0x08
+
+bool
+view_manager::do_scan (iscan::imgstream& is, const iscan::file_opener& fo,
+ bool first_time_around)
+{
+ static int status;
+
+ if (first_time_around) status = 0;
+ if (!m_filsel_cls->multi_page_mode ()) status |= SCAN_SINGLE;
+ if (wait_for_button ()) status |= SCAN_BUTTON | SCAN_NEXT;
+ if (m_scanmanager_cls->using_adf ()) status |= SCAN_ADF | SCAN_NEXT;
+
+ try
+ {
+ do
+ {
+ is.next ();
+ status &= ~SCAN_DATA;
+ scan_file (is, &status, first_time_around);
+ first_time_around = false;
+ if (status & SCAN_ERROR ) status &= ~SCAN_NEXT;
+ if (status & SCAN_CANCEL) status &= ~SCAN_NEXT;
+ if (status & SCAN_FINISH) status &= ~SCAN_NEXT;
+ if ((status & SCAN_DATA)
+ && !(status & (SCAN_ERROR | SCAN_CANCEL)))
+ {
+ is.flush ();
+ print (fo.name ());
+ }
+ }
+ while (status & SCAN_NEXT);
+ }
+ catch (std::exception& oops)
+ {
+ return false;
+ }
+
+ // Signal whether the file should be removed. Note that ADF and
+ // scan button based scanning both open a temporary file ahead of
+ // scanning. Here we signal whether that temporary file needs to
+ // be removed. Also note that ADF scans ignore the scan button
+ // setting, so we should check for ADF stuff first.
+
+ if (status & (SCAN_ERROR | SCAN_CANCEL)) return true;
+
+ if (status & SCAN_ADF)
+ if (status & SCAN_FINISH) return (status & SCAN_SINGLE);
+
+ if (status & SCAN_BUTTON)
+ if (status & SCAN_FINISH) return (status & SCAN_SINGLE);
+
+ return false;
+}
+
+int
+view_manager::do_scan_gimp (bool first_time_around)
+{
+ int cancel = 0;
+
+ if (!m_scanmanager_cls->using_adf ()
+ && !m_set.enable_start_button)
+ return scan_gimp (&cancel);
+
+ bool eos = false;
+
+ while (!eos)
+ {
+ eos = scan_gimp (&cancel, first_time_around);
+ first_time_around = false;
+ }
+ if (!cancel)
+ do_scan_gimp (first_time_around);
+
+ return PISA_ERR_SUCCESS;
+}
+
+int
+view_manager::dialog_reply( const pisa_error& err ) const
+{
+ int reply = 0;
+
+ aleart_dialog dlg;
+
+ if ( PISA_STATUS_CANCELLED == err.get_error_id() )
+ {
+ // suppress message box when user cancelled from scanner side
+ reply = SCAN_ERROR;
+ }
+ else if ( ( PISA_STATUS_GOOD < err.get_error_id() ) &&
+ (m_scanmanager_cls->using_adf ()) )
+ {
+ int i = dlg.message_box( m_main_cls->get_widget(),
+ err.get_error_string(),
+ _(" Continue "), _(" Cancel ") );
+ if (2 == i)
+ reply = SCAN_FINISH;
+ else
+ reply = SCAN_NEXT;
+ }
+ else if ( ( PISA_STATUS_GOOD == err.get_error_id() ) &&
+ m_set.enable_start_button )
+ {
+
+ int i = dlg.message_box( m_main_cls->get_widget(),
+ "Waiting for ...",
+ _(" Finish ") );
+
+ if ( 1 == i )
+ reply = SCAN_FINISH;
+ }
+ else
+ {
+ dlg.message_box( m_main_cls->get_widget(),
+ err.get_error_string() );
+
+ if (PISA_ERR_FILEOPEN != err.get_error_id())
+ reply = SCAN_ERROR;
+ }
+
+ return reply;
+}
+
+/*------------------------------------------------------------*/
+bool
+view_manager::scan_gimp (int *cancel, bool first_time_around)
+{
+
+#ifndef HAVE_ANY_GIMP
+
+ *cancel = 1; // can't scan to GIMP
+ return true;
+
+#else
+ bool error = false; // be optimistic
+ try
+ {
+ *cancel = 0;
+
+ bool reset_params = false;
+ if (wait_for_button ()
+ && m_scanmanager_cls->is_button_pressed ()) {
+ // We have an impatient user here who pressed the scanner's
+ // button *before* we even got a chance to show our WAITING
+ // message.
+ m_scanmanager_cls->clear_button_status ();
+ reset_params = true;
+ }
+
+ _feedback->set_text (wait_for_button ()
+ ? progress_window::WAITING
+ : progress_window::WARMING_UP);
+ _feedback->set_progress (0, 1);
+ _feedback->show ();
+
+ if (wait_for_button ()) {
+ long usec = m_scanmanager_cls->get_polling_time ();
+ while (!m_scanmanager_cls->is_button_pressed ()
+ && !_feedback->is_cancelled ()) {
+ microsleep (usec);
+ while (gtk_events_pending ()) {
+ gtk_main_iteration ();
+ }
+ }
+ if (_feedback->is_cancelled ()) {
+ *cancel = 1;
+ return true;
+ }
+ if (m_scanmanager_cls->push_button_needs_polling ()) {
+ m_scanmanager_cls->disable_wait_for_button();
+ }
+ }
+
+ _feedback->set_text (progress_window::WARMING_UP);
+
+ int width, height;
+ m_scanmanager_cls->init_scan (&width, &height,
+ first_time_around || reset_params);
+
+ while ( ::gtk_events_pending ( ) )
+ ::gtk_main_iteration ( );
+
+ char depth = 8;
+ int rowbytes;
+ switch (m_set.imgtype.pixeltype)
+ {
+ case PISA_PT_RGB:
+ rowbytes = width * 3;
+ break;
+ case PISA_PT_GRAY:
+ rowbytes = width;
+ break;
+ case PISA_PT_BW:
+ depth = 1;
+ rowbytes = (width + 7) / 8;
+ break;
+ default:
+ rowbytes = 0;
+ }
+
+ gimp_scan gimp_cls;
+ if (PISA_ERR_SUCCESS !=
+ gimp_cls.create_gimp_image (width, height,
+ m_set.imgtype.pixeltype, depth))
+ {
+ m_scanmanager_cls->acquire_image ( 0, 0, 1, 1 );
+
+ throw pisa_error ( PISA_ERR_OUTOFMEMORY );
+ }
+
+ for (int i = 0; i < height; i++)
+ {
+ m_scanmanager_cls->acquire_image ( gimp_cls.get_next_buf ( ),
+ rowbytes,
+ 1,
+ *cancel );
+
+ if ( i == 0 )
+ _feedback->set_text (progress_window::SCANNING);
+
+ if (*cancel)
+ {
+ error = true;
+ break;
+ }
+
+ _feedback->set_progress (i, height);
+ *cancel = _feedback->is_cancelled ();
+
+ gimp_cls.set_image_rect ( );
+
+ while ( ::gtk_events_pending ( ) )
+ ::gtk_main_iteration ( );
+ }
+ m_scanmanager_cls->acquire_image (0, 1, 1, *cancel);
+ _feedback->set_progress (height, height);
+
+ gimp_cls.finish_scan ( *cancel );
+ }
+ catch ( pisa_error & err )
+ {
+ error = true;
+ *cancel = dialog_reply( err );
+ }
+
+ m_scanmanager_cls->release_memory ();
+
+ return error;
+#endif // HAVE_ANY_GIMP
+}
+
+void
+view_manager::scan_file (iscan::imgstream& is, int *status,
+ bool first_time_around)
+{
+ try
+ {
+ bool reset_params = false;
+ if (wait_for_button ()
+ && m_scanmanager_cls->is_button_pressed ()) {
+ // We have an impatient user here who pressed the scanner's
+ // button *before* we even got a chance to show our WAITING
+ // message.
+ m_scanmanager_cls->clear_button_status ();
+ reset_params = true;
+ }
+
+ _feedback->set_text (wait_for_button ()
+ ? progress_window::WAITING
+ : progress_window::WARMING_UP);
+ _feedback->set_progress (0, 1);
+ _feedback->show ();
+
+ while (::gtk_events_pending())
+ ::gtk_main_iteration();
+
+ if (wait_for_button ()) {
+ long usec = m_scanmanager_cls->get_polling_time ();
+ while (!m_scanmanager_cls->is_button_pressed ()
+ && !_feedback->is_cancelled ()) {
+ microsleep (usec);
+ while (gtk_events_pending ()) {
+ gtk_main_iteration ();
+ }
+ }
+ if (_feedback->is_cancelled ()) {
+ // user clicked the Finish button!
+ *status |= SCAN_FINISH;
+ *status &= ~SCAN_NEXT;
+ return;
+ }
+ if (m_scanmanager_cls->push_button_needs_polling ()) {
+ m_scanmanager_cls->disable_wait_for_button();
+ }
+ }
+
+ _feedback->set_text (progress_window::WARMING_UP);
+
+ int width, height;
+ m_scanmanager_cls->init_scan (&width, &height,
+ first_time_around || reset_params);
+
+ while (::gtk_events_pending())
+ ::gtk_main_iteration();
+
+ int rowbytes;
+ iscan::colour_space cs;
+
+ switch (m_set.imgtype.pixeltype)
+ {
+ case PISA_PT_RGB:
+ rowbytes = width * 3;
+ cs = iscan::RGB;
+ break;
+ case PISA_PT_GRAY:
+ rowbytes = width;
+ cs = iscan::gray;
+ break;
+ case PISA_PT_BW:
+ rowbytes = (width + 7) / 8;
+ cs = iscan::mono;
+ break;
+ default:
+ rowbytes = 0;
+ throw pisa_error (PISA_ERR_PARAMETER);
+ }
+
+ try
+ {
+ is.size (width, height);
+ is.depth (PISA_PT_BW == m_set.imgtype.pixeltype ? 1 : 8);
+ is.colour (cs);
+ is.resolution (m_set.resolution, m_set.resolution);
+ }
+ catch (pisa_error& oops)
+ {
+ m_scanmanager_cls->acquire_image( 0, 0, 1, 1 );
+ throw oops;
+ }
+
+ unsigned char *img = new unsigned char[rowbytes];
+ for (int i = 0; i < height; ++i)
+ {
+ m_scanmanager_cls->acquire_image (img, rowbytes, 1,
+ *status & SCAN_CANCEL);
+
+ if (0 == i)
+ _feedback->set_text (progress_window::SCANNING);
+
+ if (*status & SCAN_CANCEL)
+ {
+ //error = true;
+ break;
+ }
+
+ _feedback->set_progress (i, height);
+ if (_feedback->is_cancelled ())
+ *status |= SCAN_CANCEL;
+
+ try
+ {
+ try
+ {
+ is.write ((const char *)img, rowbytes);
+ *status |= SCAN_DATA;
+ }
+ catch (std::exception& oops)
+ { // map to old API and rethrow
+ throw (pisa_error (PISA_ERR_OUTOFMEMORY));
+ }
+ }
+ catch (pisa_error& oops)
+ {
+ *status |= SCAN_CANCEL;
+
+ if (i < height)
+ m_scanmanager_cls->acquire_image (img, rowbytes, 1,
+ *status & SCAN_CANCEL);
+ aleart_dialog aleart_dlg;
+ aleart_dlg.message_box( m_main_cls->get_widget(),
+ oops.get_error_string() );
+ break;
+ }
+ while (::gtk_events_pending())
+ ::gtk_main_iteration();
+ }
+ delete[] img;
+
+ m_scanmanager_cls->acquire_image (0, 1, 1, *status & SCAN_CANCEL);
+ _feedback->set_progress (height, height);
+ }
+ catch (pisa_error& oops)
+ {
+ //error = true;
+ *status |= dialog_reply (oops);
+ }
+
+ m_scanmanager_cls->release_memory ();
+
+ while (::gtk_events_pending())
+ ::gtk_main_iteration();
+
+ return;
+}
+
+void
+view_manager::print (const std::string& filename) const
+{
+ if (PISA_DE_PRINTER == m_set.destination)
+ {
+ char cmd[1024]; // FIXME: buffer overflow!
+ sprintf (cmd, "%s %s", m_config_cls->m_cmd, filename.c_str ());
+ system (cmd); // FIXME: check cmd exit status
+
+ while (gtk_events_pending ()) gtk_main_iteration ();
+
+ remove (filename.c_str ());
+ }
+}
+
+bool
+view_manager::is_multi_image (void) const
+{
+ return (m_scanmanager_cls->using_adf () || m_set.enable_start_button);
+}
+
+bool
+view_manager::wait_for_button (void) const
+{
+ if (m_scanmanager_cls->push_button_needs_polling ())
+ {
+ return (m_set.enable_start_button);
+ }
+ else
+ {
+ return (m_set.enable_start_button && !m_scanmanager_cls->using_adf ());
+ }
+}
+
+void
+view_manager::set_resolution (long resolution)
+{
+ long adjust_res[2] = {resolution, resolution};
+
+ m_set.resolution = resolution;
+
+ /* set resolution to backend */
+ m_scanmanager_cls->get_valid_resolution (&adjust_res[0], &adjust_res[1], true);
+ m_scanmanager_cls->set_scan_resolution (adjust_res[0], adjust_res[1]);
+}
+
+void
+view_manager::set_image_type (const imagetype *type)
+{
+ memcpy (&m_set.imgtype, type, sizeof (imagetype));
+ m_scanmanager_cls->set_color_mode(type->pixeltype, type->bitdepth);
+}
+
+int
+view_manager::microsleep (size_t usec)
+{
+ struct timespec ts;
+ ts.tv_sec = usec / 1000000;
+ ts.tv_nsec = (usec % 1000000) * 1000;
+
+ return nanosleep (&ts, NULL);
+}
diff --git a/frontend/pisa_view_manager.h b/frontend/pisa_view_manager.h
new file mode 100644
index 0000000..5650c38
--- /dev/null
+++ b/frontend/pisa_view_manager.h
@@ -0,0 +1,227 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001, 2005, 2008 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___PISA_VIEW_MANAGER_H
+#define ___PISA_VIEW_MANAGER_H
+
+#include <string>
+
+#include "pisa_enums.h"
+
+#include "pisa_settings.h"
+
+#include "pisa_scan_manager.h"
+
+#include "pisa_main_window.h"
+#include "pisa_preview_window.h"
+#include "pisa_progress_window.h"
+#include "pisa_image_controls.h"
+#include "pisa_gamma_correction.h"
+#include "pisa_configuration.h"
+#include "pisa_error.h"
+#include "pisa_scan_selector.h"
+
+#include <cstring>
+
+#include "file-selector.h"
+#include "imgstream.hh"
+
+class view_manager
+{
+ public:
+
+ // operation
+ static int create_view_manager ( int argc, char * argv [ ] );
+
+ void release_view_manager ( void );
+
+ int main ( void );
+
+ void init ( void );
+ void destroy ( void );
+
+ GtkWidget * create_window ( pisa_window_id id );
+ int close_window ( pisa_window_id id, int destroy_flag );
+
+ image_controls *get_image_controls ();
+ gamma_correction *get_gamma_correction ();
+ void * get_window_cls ( pisa_window_id id );
+
+ scan_manager * get_scan_manager ( void ) { return m_scanmanager_cls; }
+
+ void sensitive ( void );
+
+ int is_gimp ( void );
+
+ void start_scan ( void );
+
+ int update_lut (long index = -1);
+ int update_lut (marquee& m);
+
+ bool change_document_source (const imagetype *);
+ void set_device ( char * name );
+ char * get_device_name() const;
+
+ const settings& get_settings (void) const;
+ const marquee& get_marquee (long index = -1) const;
+ marquee& get_marquee (long index = -1);
+
+ void add_marquee (marquee **m);
+ void del_marquee (long index = -1);
+
+ long get_marquee_size () const;
+
+ void set_destination (long destination);
+ void set_resolution (long resolution);
+ void set_image_type (const imagetype *type);
+ void set_unit (long unit);
+ void enable_start_button (bool value);
+ void enable_draft_mode (bool value);
+ void enable_unsharp_mask (bool value);
+
+ int microsleep (size_t usec);
+
+ private:
+
+ // operation
+ void open_device ( void );
+ void close_device ( void );
+
+ void load_preference ( void );
+ void save_preference ( void );
+
+ int init_img_info (void);
+
+ int init_scan_param (void);
+
+ bool do_scan (iscan::imgstream& is, const iscan::file_opener& fo,
+ bool first_time_around = true);
+ int do_scan_gimp (bool first_time_around = true);
+
+ void scan_file (iscan::imgstream& is, int *status,
+ bool first_time_around = true);
+ bool scan_gimp (int *cancel,
+ bool first_time_around = true);
+ int dialog_reply( const pisa_error& err ) const;
+
+ void print (const std::string& filename) const;
+ bool is_multi_image (void) const;
+ bool wait_for_button (void) const;
+ bool needs_duplex_rotation (void) const;
+
+ // attribute
+ settings m_set;
+
+ scan_manager * m_scanmanager_cls;
+
+ main_window * m_main_cls;
+ preview_window * m_prev_cls;
+ image_controls * m_imgctrl_cls;
+ gamma_correction * m_gamma_cls;
+ config_window * m_config_cls;
+ file_selector * m_filsel_cls;
+ scan_selector * m_scansel_cls;
+
+ progress_window *_feedback;
+
+ std::string _image_format;
+};
+
+extern view_manager * g_view_manager;
+
+inline const settings&
+view_manager::get_settings (void) const
+{
+ return m_set;
+}
+
+inline const marquee&
+view_manager::get_marquee (long index) const
+{
+ return m_set.get_marquee (index);
+}
+
+inline marquee&
+view_manager::get_marquee (long index)
+{
+ return m_set.get_marquee (index);
+}
+
+inline long
+view_manager::get_marquee_size (void) const
+{
+ return m_set.get_marquee_size ();
+}
+
+inline void
+view_manager::add_marquee (marquee **m)
+{
+ m_set.add_marquee (m);
+}
+
+inline void
+view_manager::del_marquee (long index)
+{
+ m_set.del_marquee (index);
+}
+
+inline void
+view_manager::set_destination (long destination)
+{
+ m_set.destination = destination;
+}
+
+inline void
+view_manager::set_unit (long unit)
+{
+ m_set.unit = unit;
+}
+
+inline void
+view_manager::enable_start_button (bool value)
+{
+ m_set.enable_start_button = value;
+}
+
+inline void
+view_manager::enable_draft_mode (bool value)
+{
+ m_set.enable_draft_mode = value;
+}
+
+inline void
+view_manager::enable_unsharp_mask (bool value)
+{
+ m_set.usm = (value ? 1 : 0);
+}
+
+
+#endif // ___PISA_VIEW_MANAGER_H
+
diff --git a/frontend/xpm_data.cc b/frontend/xpm_data.cc
new file mode 100644
index 0000000..26001a2
--- /dev/null
+++ b/frontend/xpm_data.cc
@@ -0,0 +1,1740 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001, 2008 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+// scan button
+const char * scan_xpm[] = {
+"36 28 257 2",
+" c None",
+". c #0C0C0C",
+"+ c #252426",
+"@ c #252526",
+"# c #262527",
+"$ c #262628",
+"% c #292929",
+"& c #2C2B2D",
+"* c #2E2E30",
+"= c #402810",
+"- c #373638",
+"; c #373737",
+"> c #3C3B3E",
+", c #3D3C3E",
+"' c #403F42",
+") c #414042",
+"! c #424043",
+"~ c #424144",
+"{ c #444444",
+"] c #49484B",
+"^ c #4A494C",
+"/ c #4E4C50",
+"( c #525154",
+"_ c #58565B",
+": c #5A585C",
+"< c #5A5A5A",
+"[ c #5B595D",
+"} c #5C5A5E",
+"| c #5D5B5F",
+"1 c #5E5C60",
+"2 c #5E5E5E",
+"3 c #5F5D61",
+"4 c #5F5F5F",
+"5 c #636166",
+"6 c #656367",
+"7 c #656368",
+"8 c #656468",
+"9 c #686868",
+"0 c #69676C",
+"a c #6A6A6A",
+"b c #6C6A6E",
+"c c #6E6C71",
+"d c #408080",
+"e c #717171",
+"f c #727075",
+"g c #737177",
+"h c #737373",
+"i c #747278",
+"j c #757575",
+"k c #767676",
+"l c #77757A",
+"m c #7A777D",
+"n c #7A787E",
+"o c #7B7B7B",
+"p c #7C7A80",
+"q c #7D7B80",
+"r c #7F7D82",
+"s c #7F7F7F",
+"t c #817E84",
+"u c #808080",
+"v c #817F85",
+"w c #827F85",
+"x c #818181",
+"y c #828086",
+"z c #848288",
+"A c #E63A3A",
+"B c #89868D",
+"C c #89878D",
+"D c None",
+"E c #8A888E",
+"F c #8B898F",
+"G c #8C8990",
+"H c #8D8A91",
+"I c #8D8B91",
+"J c #8E8B91",
+"K c #8F8D93",
+"L c #908E94",
+"M c #F63E3E",
+"N c #928F96",
+"O c #939097",
+"P c #3E9AF6",
+"Q c #949198",
+"R c #959299",
+"S c #949494",
+"T c #96949A",
+"U c #FF4040",
+"V c #97949B",
+"W c #97959B",
+"X c #809F9F",
+"Y c #979797",
+"Z c #40A0FF",
+"` c #9A979E",
+" . c #999999",
+".. c #9B989F",
+"+. c #9C99A0",
+"@. c #9D9AA1",
+"#. c #9D9BA2",
+"$. c #9E9BA3",
+"%. c #A09DA4",
+"&. c #A29FA6",
+"*. c #A5A2AA",
+"=. c #A6A3AB",
+"-. c #A8A5AC",
+";. c #AAA7AE",
+">. c #ABA7AF",
+",. c #60C0C0",
+"'. c #ABA8B0",
+"). c #ACA9B1",
+"!. c #ADAAB2",
+"~. c #AEABB3",
+"{. c #B0ADB5",
+"]. c #B1ADB5",
+"^. c #B0B0B0",
+"/. c #B2AEB6",
+"(. c #B2AFB7",
+"_. c #B3B0B8",
+":. c #B5B2BA",
+"<. c #B6B3BB",
+"[. c #B7B4BC",
+"}. c #B8B4BD",
+"|. c #B7B7B7",
+"1. c #B9B5BD",
+"2. c #B9B6BE",
+"3. c #BBB7BF",
+"4. c #BBB8C0",
+"5. c #BCB9C1",
+"6. c #BDBAC2",
+"7. c #FFA040",
+"8. c #BEBAC3",
+"9. c #BDBDBD",
+"0. c #BFBBC4",
+"a. c #C0BCC5",
+"b. c #C0BDC6",
+"c. c #C1BEC6",
+"d. c #C0C0C0",
+"e. c #C2BFC7",
+"f. c #C3C0C8",
+"g. c #C5C1CA",
+"h. c #90E63A",
+"i. c #C6C2CB",
+"j. c #C7C3CC",
+"k. c #C7C4CD",
+"l. c #C8C5CD",
+"m. c #C8C5CE",
+"n. c #C9C6CF",
+"o. c #CAC6CF",
+"p. c #CBC7D0",
+"q. c #CCC8D1",
+"r. c #CCCCCC",
+"s. c #CECAD3",
+"t. c #CECBD4",
+"u. c #CFCCD5",
+"v. c #D0CCD6",
+"w. c #D1CDD7",
+"x. c #D2CED8",
+"y. c #D3CFD8",
+"z. c #9AF63E",
+"A. c #D4D0DA",
+"B. c #D3D3D3",
+"C. c #D5D1DA",
+"D. c #D6D2DC",
+"E. c #D7D3DD",
+"F. c #D8D4DE",
+"G. c #D9D5DF",
+"H. c #DAD6E0",
+"I. c #A0FF40",
+"J. c #DBD7E1",
+"K. c #DCD8E1",
+"L. c #DCD8E2",
+"M. c #DDD9E3",
+"N. c #DEDAE4",
+"O. c #DFDBE5",
+"P. c #80FFFF",
+"Q. c #E3E3E3",
+"R. c #E6E6E6",
+"S. c #F6F6F6",
+"T. c #FCFCFC",
+"U. c #FFFFFF",
+"V. c #000000",
+"W. c #000000",
+"X. c #000000",
+"Y. c #000000",
+"Z. c #000000",
+"`. c #000000",
+" + c #000000",
+".+ c #000000",
+"++ c #000000",
+"@+ c #000000",
+"#+ c #000000",
+"$+ c #000000",
+"%+ c #000000",
+"&+ c #000000",
+"*+ c #000000",
+"=+ c #000000",
+"-+ c #000000",
+";+ c #000000",
+">+ c #000000",
+",+ c #000000",
+"'+ c #000000",
+")+ c #000000",
+"!+ c #000000",
+"~+ c #000000",
+"{+ c #000000",
+"]+ c #000000",
+"^+ c #000000",
+"/+ c #000000",
+"(+ c #000000",
+"_+ c #000000",
+":+ c #000000",
+"<+ c #000000",
+"[+ c #000000",
+"}+ c #000000",
+"|+ c #000000",
+"1+ c #000000",
+"2+ c #000000",
+"3+ c #000000",
+"4+ c #000000",
+"5+ c #000000",
+"6+ c #000000",
+"7+ c #000000",
+"8+ c #000000",
+"9+ c #000000",
+"0+ c #000000",
+"a+ c #000000",
+"b+ c #000000",
+"c+ c #000000",
+"d+ c #000000",
+"e+ c #000000",
+"f+ c #000000",
+"g+ c #000000",
+"h+ c #000000",
+"i+ c #000000",
+"j+ c #000000",
+"k+ c #000000",
+"l+ c #000000",
+"m+ c #000000",
+"n+ c #000000",
+"o+ c #000000",
+"p+ c #000000",
+"q+ c #000000",
+"r+ c #000000",
+"s+ c #000000",
+"t+ c #000000",
+"u+ c #000000",
+"v+ c #000000",
+"w+ c #000000",
+"x+ c #000000",
+"y+ c #000000",
+"z+ c #000000",
+"A+ c #000000",
+"B+ c #000000",
+"C+ c #000000",
+"D+ c #000000",
+"E+ c #000000",
+"F+ c #000000",
+"G+ c #000000",
+"H+ c #000000",
+"D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D ",
+"D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D ",
+"D D D D D &.Q K N +.>.[.4.0.D D D D D D D D D D D D D D D D D D D D D D ",
+"D D D D D . . . . .n t C L +.>.[.D D D D D D D D D D D D D D D D D D D ",
+"D D D D D !.r.d.d.d. . . . . .n t C J K N +.D D D D D D D D D D D D D D ",
+"D D D D D D -.r.r.r.d.d.d.d.d. . . . . .u u u H *.D D D D D D D D D D D ",
+"D D D D D D D D T y r.r.r.d.d.d.d.d.d.d.d.u u u u F *.D D D D D D D D D ",
+"D D D D D D D D D _.#.t 6 r.r.r.d.d.d.d.d.d.d.u u u u F *.D D D D D D D ",
+"D D D D D D D D D 5.*.E f } d.d.r.r.d.d.d.d.d.d.d.u u u u F =.D D D D D ",
+"D D D D D D D D /.V l m d.d.d.d.u u r.u d.d.d.d.d.d.d.u u u u O }.D D D ",
+"D D D D D D /...w 7 d.d.U.U.u { U.d d d u u u u u u d.d.d.u u u u D D D ",
+"D D D D /...w 7 d.d.U.U.u u U.,.U.,.,.d d d d d d d { u { { { { { R D D ",
+"D D /...w 7 d.d.U.U.u u U.U.U.,.U.,.,.,.,.d d d d d u u d.d.{ { u { !.D ",
+"D ;.z 7 d.d.U.U.u X ,.,.U.U.U.,.U.,.,.,.,.,.,.d u u d.d.{ { u u u { +.D ",
+"D T d.d.U.U.u u ,.,.,.,.U.U.U.,.U.,.,.,.,.,.d.u d.d.{ { u u u u u { O D ",
+"D L d.d.d.U.U.U.u X ,.,.U.U.U.,.U.,.,.,.d.d.U.U.{ { u u u u u u u { R D ",
+"D K d.U.U.d.d.U.U.U.u u U.U.U.,.U.,.d.d.U.U.{ { u u u u u u u u { t &.D ",
+"D N d.U.U.U.U.d.d.U.U.U.u u U.,.d.d.U.U.{ { u u u u u u u u { { t D D D ",
+"D $.d.U.u U.U.U.U.d.d.U.U.U.u u U.U.{ { u u u u u u u u { { p G D D D D ",
+"D :.d.U.U.u U.U.U.U.U.d.d.U.U.U.{ { u u u u u u u u { { p G D D D D D D ",
+"D D !.u u U.U.U.U.U.U.U.U.d.{ { u u u u u u u u { { p G D D D D D D D D ",
+"D D D ~.H u u U.U.U.U.U.U.U.{ u u u u u u u { { p G $.D D D D D D D D D ",
+"D D D D D =.E u u U.U.U.U.U.{ u u u u u { { p G $.D D D D D D D D D D D ",
+"D D D D D D D =.E u u U.U.U.{ u u u { { p G $.D D D D D D D D D D D D D ",
+"D D D D D D D D D =.E u u U.{ u { { p G D D D D D D D D D D D D D D D D ",
+"D D D D D D D D D D D =.G u { { p G D D D D D D D D D D D D D D D D D D ",
+"D D D D D D D D D D D D D !.#.W %.D D D D D D D D D D D D D D D D D D D ",
+"D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D "};
+
+// preview button
+const char * preview_xpm[] = {
+"18 18 257 2",
+" c None",
+". c #0C0C0C",
+"+ c #252426",
+"@ c #252526",
+"# c #262527",
+"$ c #262628",
+"% c #292929",
+"& c #2C2B2D",
+"* c #2E2E30",
+"= c #402810",
+"- c #373638",
+"; c #373737",
+"> c #3C3B3E",
+", c #3D3C3E",
+"' c #403F42",
+") c #414042",
+"! c #424043",
+"~ c #424144",
+"{ c #444444",
+"] c #49484B",
+"^ c #4A494C",
+"/ c #4E4C50",
+"( c #525154",
+"_ c #58565B",
+": c #5A585C",
+"< c #5A5A5A",
+"[ c #5B595D",
+"} c #5C5A5E",
+"| c #5D5B5F",
+"1 c #5E5C60",
+"2 c #5E5E5E",
+"3 c #5F5D61",
+"4 c #5F5F5F",
+"5 c #636166",
+"6 c #656367",
+"7 c #656368",
+"8 c #656468",
+"9 c #686868",
+"0 c #69676C",
+"a c #6A6A6A",
+"b c #6C6A6E",
+"c c #6E6C71",
+"d c #408080",
+"e c #717171",
+"f c #727075",
+"g c #737177",
+"h c #737373",
+"i c #747278",
+"j c #757575",
+"k c #767676",
+"l c #77757A",
+"m c #7A777D",
+"n c #7A787E",
+"o c #7B7B7B",
+"p c #7C7A80",
+"q c #7D7B80",
+"r c #7F7D82",
+"s c #7F7F7F",
+"t c #817E84",
+"u c #808080",
+"v c #817F85",
+"w c #827F85",
+"x c #818181",
+"y c #828086",
+"z c #848288",
+"A c #E63A3A",
+"B c #89868D",
+"C c #89878D",
+"D c None",
+"E c #8A888E",
+"F c #8B898F",
+"G c #8C8990",
+"H c #8D8A91",
+"I c #8D8B91",
+"J c #8E8B91",
+"K c #8F8D93",
+"L c #908E94",
+"M c #F63E3E",
+"N c #928F96",
+"O c #939097",
+"P c #3E9AF6",
+"Q c #949198",
+"R c #959299",
+"S c #949494",
+"T c #96949A",
+"U c #FF4040",
+"V c #97949B",
+"W c #97959B",
+"X c #809F9F",
+"Y c #979797",
+"Z c #40A0FF",
+"` c #9A979E",
+" . c #999999",
+".. c #9B989F",
+"+. c #9C99A0",
+"@. c #9D9AA1",
+"#. c #9D9BA2",
+"$. c #9E9BA3",
+"%. c #A09DA4",
+"&. c #A29FA6",
+"*. c #A5A2AA",
+"=. c #A6A3AB",
+"-. c #A8A5AC",
+";. c #AAA7AE",
+">. c #ABA7AF",
+",. c #60C0C0",
+"'. c #ABA8B0",
+"). c #ACA9B1",
+"!. c #ADAAB2",
+"~. c #AEABB3",
+"{. c #B0ADB5",
+"]. c #B1ADB5",
+"^. c #B0B0B0",
+"/. c #B2AEB6",
+"(. c #B2AFB7",
+"_. c #B3B0B8",
+":. c #B5B2BA",
+"<. c #B6B3BB",
+"[. c #B7B4BC",
+"}. c #B8B4BD",
+"|. c #B7B7B7",
+"1. c #B9B5BD",
+"2. c #B9B6BE",
+"3. c #BBB7BF",
+"4. c #BBB8C0",
+"5. c #BCB9C1",
+"6. c #BDBAC2",
+"7. c #FFA040",
+"8. c #BEBAC3",
+"9. c #BDBDBD",
+"0. c #BFBBC4",
+"a. c #C0BCC5",
+"b. c #C0BDC6",
+"c. c #C1BEC6",
+"d. c #C0C0C0",
+"e. c #C2BFC7",
+"f. c #C3C0C8",
+"g. c #C5C1CA",
+"h. c #90E63A",
+"i. c #C6C2CB",
+"j. c #C7C3CC",
+"k. c #C7C4CD",
+"l. c #C8C5CD",
+"m. c #C8C5CE",
+"n. c #C9C6CF",
+"o. c #CAC6CF",
+"p. c #CBC7D0",
+"q. c #CCC8D1",
+"r. c #CCCCCC",
+"s. c #CECAD3",
+"t. c #CECBD4",
+"u. c #CFCCD5",
+"v. c #D0CCD6",
+"w. c #D1CDD7",
+"x. c #D2CED8",
+"y. c #D3CFD8",
+"z. c #9AF63E",
+"A. c #D4D0DA",
+"B. c #D3D3D3",
+"C. c #D5D1DA",
+"D. c #D6D2DC",
+"E. c #D7D3DD",
+"F. c #D8D4DE",
+"G. c #D9D5DF",
+"H. c #DAD6E0",
+"I. c #A0FF40",
+"J. c #DBD7E1",
+"K. c #DCD8E1",
+"L. c #DCD8E2",
+"M. c #DDD9E3",
+"N. c #DEDAE4",
+"O. c #DFDBE5",
+"P. c #80FFFF",
+"Q. c #E3E3E3",
+"R. c #E6E6E6",
+"S. c #F6F6F6",
+"T. c #FCFCFC",
+"U. c #FFFFFF",
+"V. c #000000",
+"W. c #000000",
+"X. c #000000",
+"Y. c #000000",
+"Z. c #000000",
+"`. c #000000",
+" + c #000000",
+".+ c #000000",
+"++ c #000000",
+"@+ c #000000",
+"#+ c #000000",
+"$+ c #000000",
+"%+ c #000000",
+"&+ c #000000",
+"*+ c #000000",
+"=+ c #000000",
+"-+ c #000000",
+";+ c #000000",
+">+ c #000000",
+",+ c #000000",
+"'+ c #000000",
+")+ c #000000",
+"!+ c #000000",
+"~+ c #000000",
+"{+ c #000000",
+"]+ c #000000",
+"^+ c #000000",
+"/+ c #000000",
+"(+ c #000000",
+"_+ c #000000",
+":+ c #000000",
+"<+ c #000000",
+"[+ c #000000",
+"}+ c #000000",
+"|+ c #000000",
+"1+ c #000000",
+"2+ c #000000",
+"3+ c #000000",
+"4+ c #000000",
+"5+ c #000000",
+"6+ c #000000",
+"7+ c #000000",
+"8+ c #000000",
+"9+ c #000000",
+"0+ c #000000",
+"a+ c #000000",
+"b+ c #000000",
+"c+ c #000000",
+"d+ c #000000",
+"e+ c #000000",
+"f+ c #000000",
+"g+ c #000000",
+"h+ c #000000",
+"i+ c #000000",
+"j+ c #000000",
+"k+ c #000000",
+"l+ c #000000",
+"m+ c #000000",
+"n+ c #000000",
+"o+ c #000000",
+"p+ c #000000",
+"q+ c #000000",
+"r+ c #000000",
+"s+ c #000000",
+"t+ c #000000",
+"u+ c #000000",
+"v+ c #000000",
+"w+ c #000000",
+"x+ c #000000",
+"y+ c #000000",
+"z+ c #000000",
+"A+ c #000000",
+"B+ c #000000",
+"C+ c #000000",
+"D+ c #000000",
+"E+ c #000000",
+"F+ c #000000",
+"G+ c #000000",
+"H+ c #000000",
+"D r.r.r.r.r.r.r.r.r.r.r.-.D D D D D ",
+"D r.U.U.U.U.U.U.U.U.r.U.u K D D D D ",
+"D r.U.U.U.U.U.U.U.U.r.U.U.u K D D D ",
+"D r.U.U.U.U.U.U.U.U.u u u u 0 D D D ",
+"D r.U.U.U.U.U.U.U.U.U.U.U.u ( ` D D ",
+"D r.U.U.U.U.U.U.U.U.U.U.U.u ^ R D D ",
+"D r.U.U.U.U.U.U.U.U.U.U.U.u ^ R D D ",
+"D r.U.U.U.U.U.U.U.U.U.U.U.u ^ R D D ",
+"D r.U.U.U.U.U.U.U.U.U.U.U.u ^ R D D ",
+"D r.U.U.U.U.U.U.U.U.U.U.U.u ^ R D D ",
+"D r.U.U.U.U.U.U.U.U.U.U.U.u ^ R D D ",
+"D r.U.U.U.U.U.U.U.U.U.U.U.u ^ R D D ",
+"D r.U.U.U.U.U.U.U.U.U.U.U.u ^ R D D ",
+"D r.U.U.U.U.U.U.U.U.U.U.U.u ^ R D D ",
+"D r.U.U.U.U.U.U.U.U.U.U.U.u ^ R D D ",
+"D r.u u u u u u u u u u u u [ @.D D ",
+"D !.p [ ^ ^ ^ ^ ^ ^ ^ ^ ^ [ p D D D ",
+"D D d.@.R R R R R R R R R @.D D D D "};
+
+// zoom
+const char * zoom_xpm[] = {
+"18 18 257 2",
+" c None",
+". c #0C0C0C",
+"+ c #252426",
+"@ c #252526",
+"# c #262527",
+"$ c #262628",
+"% c #292929",
+"& c #2C2B2D",
+"* c #2E2E30",
+"= c #402810",
+"- c #373638",
+"; c #373737",
+"> c #3C3B3E",
+", c #3D3C3E",
+"' c #403F42",
+") c #414042",
+"! c #424043",
+"~ c #424144",
+"{ c #444444",
+"] c #49484B",
+"^ c #4A494C",
+"/ c #4E4C50",
+"( c #525154",
+"_ c #58565B",
+": c #5A585C",
+"< c #5A5A5A",
+"[ c #5B595D",
+"} c #5C5A5E",
+"| c #5D5B5F",
+"1 c #5E5C60",
+"2 c #5E5E5E",
+"3 c #5F5D61",
+"4 c #5F5F5F",
+"5 c #636166",
+"6 c #656367",
+"7 c #656368",
+"8 c #656468",
+"9 c #686868",
+"0 c #69676C",
+"a c #6A6A6A",
+"b c #6C6A6E",
+"c c #6E6C71",
+"d c #408080",
+"e c #717171",
+"f c #727075",
+"g c #737177",
+"h c #737373",
+"i c #747278",
+"j c #757575",
+"k c #767676",
+"l c #77757A",
+"m c #7A777D",
+"n c #7A787E",
+"o c #7B7B7B",
+"p c #7C7A80",
+"q c #7D7B80",
+"r c #7F7D82",
+"s c #7F7F7F",
+"t c #817E84",
+"u c #808080",
+"v c #817F85",
+"w c #827F85",
+"x c #818181",
+"y c #828086",
+"z c #848288",
+"A c #E63A3A",
+"B c #89868D",
+"C c #89878D",
+"D c None",
+"E c #8A888E",
+"F c #8B898F",
+"G c #8C8990",
+"H c #8D8A91",
+"I c #8D8B91",
+"J c #8E8B91",
+"K c #8F8D93",
+"L c #908E94",
+"M c #F63E3E",
+"N c #928F96",
+"O c #939097",
+"P c #3E9AF6",
+"Q c #949198",
+"R c #959299",
+"S c #949494",
+"T c #96949A",
+"U c #FF4040",
+"V c #97949B",
+"W c #97959B",
+"X c #809F9F",
+"Y c #979797",
+"Z c #40A0FF",
+"` c #9A979E",
+" . c #999999",
+".. c #9B989F",
+"+. c #9C99A0",
+"@. c #9D9AA1",
+"#. c #9D9BA2",
+"$. c #9E9BA3",
+"%. c #A09DA4",
+"&. c #A29FA6",
+"*. c #A5A2AA",
+"=. c #A6A3AB",
+"-. c #A8A5AC",
+";. c #AAA7AE",
+">. c #ABA7AF",
+",. c #60C0C0",
+"'. c #ABA8B0",
+"). c #ACA9B1",
+"!. c #ADAAB2",
+"~. c #AEABB3",
+"{. c #B0ADB5",
+"]. c #B1ADB5",
+"^. c #B0B0B0",
+"/. c #B2AEB6",
+"(. c #B2AFB7",
+"_. c #B3B0B8",
+":. c #B5B2BA",
+"<. c #B6B3BB",
+"[. c #B7B4BC",
+"}. c #B8B4BD",
+"|. c #B7B7B7",
+"1. c #B9B5BD",
+"2. c #B9B6BE",
+"3. c #BBB7BF",
+"4. c #BBB8C0",
+"5. c #BCB9C1",
+"6. c #BDBAC2",
+"7. c #FFA040",
+"8. c #BEBAC3",
+"9. c #BDBDBD",
+"0. c #BFBBC4",
+"a. c #C0BCC5",
+"b. c #C0BDC6",
+"c. c #C1BEC6",
+"d. c #C0C0C0",
+"e. c #C2BFC7",
+"f. c #C3C0C8",
+"g. c #C5C1CA",
+"h. c #90E63A",
+"i. c #C6C2CB",
+"j. c #C7C3CC",
+"k. c #C7C4CD",
+"l. c #C8C5CD",
+"m. c #C8C5CE",
+"n. c #C9C6CF",
+"o. c #CAC6CF",
+"p. c #CBC7D0",
+"q. c #CCC8D1",
+"r. c #CCCCCC",
+"s. c #CECAD3",
+"t. c #CECBD4",
+"u. c #CFCCD5",
+"v. c #D0CCD6",
+"w. c #D1CDD7",
+"x. c #D2CED8",
+"y. c #D3CFD8",
+"z. c #9AF63E",
+"A. c #D4D0DA",
+"B. c #D3D3D3",
+"C. c #D5D1DA",
+"D. c #D6D2DC",
+"E. c #D7D3DD",
+"F. c #D8D4DE",
+"G. c #D9D5DF",
+"H. c #DAD6E0",
+"I. c #A0FF40",
+"J. c #DBD7E1",
+"K. c #DCD8E1",
+"L. c #DCD8E2",
+"M. c #DDD9E3",
+"N. c #DEDAE4",
+"O. c #DFDBE5",
+"P. c #80FFFF",
+"Q. c #E3E3E3",
+"R. c #E6E6E6",
+"S. c #F6F6F6",
+"T. c #FCFCFC",
+"U. c #FFFFFF",
+"V. c #000000",
+"W. c #000000",
+"X. c #000000",
+"Y. c #000000",
+"Z. c #000000",
+"`. c #000000",
+" + c #000000",
+".+ c #000000",
+"++ c #000000",
+"@+ c #000000",
+"#+ c #000000",
+"$+ c #000000",
+"%+ c #000000",
+"&+ c #000000",
+"*+ c #000000",
+"=+ c #000000",
+"-+ c #000000",
+";+ c #000000",
+">+ c #000000",
+",+ c #000000",
+"'+ c #000000",
+")+ c #000000",
+"!+ c #000000",
+"~+ c #000000",
+"{+ c #000000",
+"]+ c #000000",
+"^+ c #000000",
+"/+ c #000000",
+"(+ c #000000",
+"_+ c #000000",
+":+ c #000000",
+"<+ c #000000",
+"[+ c #000000",
+"}+ c #000000",
+"|+ c #000000",
+"1+ c #000000",
+"2+ c #000000",
+"3+ c #000000",
+"4+ c #000000",
+"5+ c #000000",
+"6+ c #000000",
+"7+ c #000000",
+"8+ c #000000",
+"9+ c #000000",
+"0+ c #000000",
+"a+ c #000000",
+"b+ c #000000",
+"c+ c #000000",
+"d+ c #000000",
+"e+ c #000000",
+"f+ c #000000",
+"g+ c #000000",
+"h+ c #000000",
+"i+ c #000000",
+"j+ c #000000",
+"k+ c #000000",
+"l+ c #000000",
+"m+ c #000000",
+"n+ c #000000",
+"o+ c #000000",
+"p+ c #000000",
+"q+ c #000000",
+"r+ c #000000",
+"s+ c #000000",
+"t+ c #000000",
+"u+ c #000000",
+"v+ c #000000",
+"w+ c #000000",
+"x+ c #000000",
+"y+ c #000000",
+"z+ c #000000",
+"A+ c #000000",
+"B+ c #000000",
+"C+ c #000000",
+"D+ c #000000",
+"E+ c #000000",
+"F+ c #000000",
+"G+ c #000000",
+"H+ c #000000",
+"r.r.r.r.r.r.r.r.r.r.r.-.D D D D D D ",
+"r.U.U.U.U.U.U.U.U.r.U.u K D D D D D ",
+"r.U.U.U.U.U.U.U.U.r.U.U.u K D D D D ",
+"r.U.U.U.T.S.R.d.d.d.d.4 a 3 D D D D ",
+"r.U.U.U.S.d.d.P.P.P.P.d.d., B D D D ",
+"r.U.U.U.R.d.P.P.U.P.P.P.u @ c D D D ",
+"r.U.U.U.d.P.P.U.U.U.U.P.P.u _ D D D ",
+"r.U.U.U.d.P.U.U.U.P.P.P.P.u ~ D D D ",
+"r.U.U.U.d.P.P.U.P.P.P.P.P.u - D D D ",
+"r.U.U.U.d.P.P.U.P.P.P.P.P.u - ` D D ",
+"r.U.U.U.9.d.P.P.P.P.P.P.u . , %.D D ",
+"r.U.U.U.B.d.u P.P.P.P.u u 7.' #.D D ",
+"r.U.U.U.R.9.x u u u u % = = 7.z D D ",
+"r.U.U.U.S.Q.9.Y e 2 2 9 ; = = 7.H D ",
+"r.U.U.U.T.S.R.B.9.^.^.|.< & = = 7.N ",
+"r.u u u u u s o k h h j h ] 5 = = r ",
+"D p [ ^ ^ ^ ^ ^ ^ ^ ^ ^ : i D N r G ",
+"D D D D D D D D D D D D D D D D D D "};
+
+// auto
+const char * auto_xpm[] = {
+"18 18 257 2",
+" c None",
+". c #0C0C0C",
+"+ c #252426",
+"@ c #252526",
+"# c #262527",
+"$ c #262628",
+"% c #292929",
+"& c #2C2B2D",
+"* c #2E2E30",
+"= c #402810",
+"- c #373638",
+"; c #373737",
+"> c #3C3B3E",
+", c #3D3C3E",
+"' c #403F42",
+") c #414042",
+"! c #424043",
+"~ c #424144",
+"{ c #444444",
+"] c #49484B",
+"^ c #4A494C",
+"/ c #4E4C50",
+"( c #525154",
+"_ c #58565B",
+": c #5A585C",
+"< c #5A5A5A",
+"[ c #5B595D",
+"} c #5C5A5E",
+"| c #5D5B5F",
+"1 c #5E5C60",
+"2 c #5E5E5E",
+"3 c #5F5D61",
+"4 c #5F5F5F",
+"5 c #636166",
+"6 c #656367",
+"7 c #656368",
+"8 c #656468",
+"9 c #686868",
+"0 c #69676C",
+"a c #6A6A6A",
+"b c #6C6A6E",
+"c c #6E6C71",
+"d c #408080",
+"e c #717171",
+"f c #727075",
+"g c #737177",
+"h c #737373",
+"i c #747278",
+"j c #757575",
+"k c #767676",
+"l c #77757A",
+"m c #7A777D",
+"n c #7A787E",
+"o c #7B7B7B",
+"p c #7C7A80",
+"q c #7D7B80",
+"r c #7F7D82",
+"s c #7F7F7F",
+"t c #817E84",
+"u c #808080",
+"v c #817F85",
+"w c #827F85",
+"x c #818181",
+"y c #828086",
+"z c #848288",
+"A c #E63A3A",
+"B c #89868D",
+"C c #89878D",
+"D c #3A90E6",
+"E c #8A888E",
+"F c #8B898F",
+"G c #8C8990",
+"H c #8D8A91",
+"I c #8D8B91",
+"J c #8E8B91",
+"K c #8F8D93",
+"L c #908E94",
+"M c #F63E3E",
+"N c #928F96",
+"O c #939097",
+"P c #3E9AF6",
+"Q c #949198",
+"R c #959299",
+"S c #949494",
+"T c #96949A",
+"U c #FF4040",
+"V c #97949B",
+"W c #97959B",
+"X c #809F9F",
+"Y c #979797",
+"Z c #40A0FF",
+"` c #9A979E",
+" . c #999999",
+".. c #9B989F",
+"+. c #9C99A0",
+"@. c #9D9AA1",
+"#. c #9D9BA2",
+"$. c #9E9BA3",
+"%. c #A09DA4",
+"&. c #A29FA6",
+"*. c #A5A2AA",
+"=. c #A6A3AB",
+"-. c #A8A5AC",
+";. c #AAA7AE",
+">. c #ABA7AF",
+",. c #60C0C0",
+"'. c #ABA8B0",
+"). c #ACA9B1",
+"!. c #ADAAB2",
+"~. c #AEABB3",
+"{. c #B0ADB5",
+"]. c #B1ADB5",
+"^. c #B0B0B0",
+"/. c #B2AEB6",
+"(. c #B2AFB7",
+"_. c #B3B0B8",
+":. c #B5B2BA",
+"<. c #B6B3BB",
+"[. c #B7B4BC",
+"}. c #B8B4BD",
+"|. c #B7B7B7",
+"1. c #B9B5BD",
+"2. c #B9B6BE",
+"3. c #BBB7BF",
+"4. c #BBB8C0",
+"5. c #BCB9C1",
+"6. c #BDBAC2",
+"7. c #FFA040",
+"8. c #BEBAC3",
+"9. c #BDBDBD",
+"0. c #BFBBC4",
+"a. c #C0BCC5",
+"b. c #C0BDC6",
+"c. c #C1BEC6",
+"d. c #C0C0C0",
+"e. c #C2BFC7",
+"f. c #C3C0C8",
+"g. c #C5C1CA",
+"h. c #90E63A",
+"i. c #C6C2CB",
+"j. c #C7C3CC",
+"k. c #C7C4CD",
+"l. c #C8C5CD",
+"m. c #C8C5CE",
+"n. c #C9C6CF",
+"o. c #CAC6CF",
+"p. c #CBC7D0",
+"q. c #CCC8D1",
+"r. c #CCCCCC",
+"s. c #CECAD3",
+"t. c #CECBD4",
+"u. c #CFCCD5",
+"v. c #D0CCD6",
+"w. c #D1CDD7",
+"x. c #D2CED8",
+"y. c #D3CFD8",
+"z. c #9AF63E",
+"A. c #D4D0DA",
+"B. c #D3D3D3",
+"C. c #D5D1DA",
+"D. c #D6D2DC",
+"E. c #D7D3DD",
+"F. c #D8D4DE",
+"G. c #D9D5DF",
+"H. c #DAD6E0",
+"I. c #A0FF40",
+"J. c #DBD7E1",
+"K. c #DCD8E1",
+"L. c #DCD8E2",
+"M. c #DDD9E3",
+"N. c #DEDAE4",
+"O. c #DFDBE5",
+"P. c #80FFFF",
+"Q. c #E3E3E3",
+"R. c #E6E6E6",
+"S. c #F6F6F6",
+"T. c #FCFCFC",
+"U. c None",
+"V. c #000000",
+"W. c #000000",
+"X. c #000000",
+"Y. c #000000",
+"Z. c #000000",
+"`. c #000000",
+" + c #000000",
+".+ c #000000",
+"++ c #000000",
+"@+ c #000000",
+"#+ c #000000",
+"$+ c #000000",
+"%+ c #000000",
+"&+ c #000000",
+"*+ c #000000",
+"=+ c #000000",
+"-+ c #000000",
+";+ c #000000",
+">+ c #000000",
+",+ c #000000",
+"'+ c #000000",
+")+ c #000000",
+"!+ c #000000",
+"~+ c #000000",
+"{+ c #000000",
+"]+ c #000000",
+"^+ c #000000",
+"/+ c #000000",
+"(+ c #000000",
+"_+ c #000000",
+":+ c #000000",
+"<+ c #000000",
+"[+ c #000000",
+"}+ c #000000",
+"|+ c #000000",
+"1+ c #000000",
+"2+ c #000000",
+"3+ c #000000",
+"4+ c #000000",
+"5+ c #000000",
+"6+ c #000000",
+"7+ c #000000",
+"8+ c #000000",
+"9+ c #000000",
+"0+ c #000000",
+"a+ c #000000",
+"b+ c #000000",
+"c+ c #000000",
+"d+ c #000000",
+"e+ c #000000",
+"f+ c #000000",
+"g+ c #000000",
+"h+ c #000000",
+"i+ c #000000",
+"j+ c #000000",
+"k+ c #000000",
+"l+ c #000000",
+"m+ c #000000",
+"n+ c #000000",
+"o+ c #000000",
+"p+ c #000000",
+"q+ c #000000",
+"r+ c #000000",
+"s+ c #000000",
+"t+ c #000000",
+"u+ c #000000",
+"v+ c #000000",
+"w+ c #000000",
+"x+ c #000000",
+"y+ c #000000",
+"z+ c #000000",
+"A+ c #000000",
+"B+ c #000000",
+"C+ c #000000",
+"D+ c #000000",
+"E+ c #000000",
+"F+ c #000000",
+"G+ c #000000",
+"H+ c #000000",
+"U.U.U.U.U. . .U.U.U.U.U.U.U.U.U.U.U.",
+"U.U.U. . .O.Z .R U.U.U.U.U.U.U.U.U.",
+"U. . .O.O.Z O. .8 U.U.U.U.U.U.U.U.U.",
+" .O.O.U O.I.O.Z Y m . .U.U.U.U.U.U.",
+" .O.U O.I.O.Z O.S .U U . .#.U.U.U.",
+">. .O.O.U O.I.L.D .U U I.I. . .%.U.",
+"U. .O.U O.I.O.P .U U I.I.I.Z Z .R ",
+"U.#. .O.O.U L.h. .U U I.I.Z Z Z .g ",
+"U.U. .O.U O.z. .U U I.I.I.Z Z .* b ",
+"U.U.#. .O.L.A .U U I.I.Z Z Z .! U.",
+"U.U.U. .O.M .U U I.I.I.Z Z .$ 1 U.",
+"U.U.U.#.Y n. .U U I.I.Z Z Z .! v U.",
+"U.U.U.U.S .U U I.I.I.Z Z .+ 1 U.U.",
+"U.U.U.U.T .U U I.I.Z Z Z .) U.U.U.",
+"U.U.U.U.U.g . .I.I.Z Z .# 1 U.U.U.",
+"U.U.U.U.U.&.g / . .Z Z .! U.U.U.U.",
+"U.U.U.U.U.U.U.T q | . .> U.U.U.U.U.",
+"U.U.U.U.U.U.U.U.U.U.v b U.U.U.U.U.U."};
+
+// gamma correction mono
+const char * gamma_m_xpm[] = {
+"30 20 31 1",
+" c None",
+". c #000000",
+"+ c #080808",
+"@ c #111111",
+"# c #1A1A1A",
+"$ c #232323",
+"% c #2B2B2B",
+"& c #343434",
+"* c #3D3D3D",
+"= c #464646",
+"- c #4F4F4F",
+"; c #575757",
+"> c #606060",
+", c #696969",
+"' c #727272",
+") c #7B7B7B",
+"! c #838383",
+"~ c #8C8C8C",
+"{ c #959595",
+"] c #9E9E9E",
+"^ c #A7A7A7",
+"/ c #AFAFAF",
+"( c #B8B8B8",
+"_ c #C1C1C1",
+": c #CACACA",
+"< c #D3D3D3",
+"[ c #DBDBDB",
+"} c #E4E4E4",
+"| c #EDEDED",
+"1 c #F6F6F6",
+"2 c #FFFFFF",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12"};
+
+
+// gamma correction red
+const char * gamma_r_xpm[] = {
+"30 20 31 1",
+" c None",
+". c #FF0000",
+"+ c #FF0808",
+"@ c #FF1111",
+"# c #FF1A1A",
+"$ c #FF2323",
+"% c #FF2B2B",
+"& c #FF3434",
+"* c #FF3D3D",
+"= c #FF4646",
+"- c #FF4F4F",
+"; c #FF5757",
+"> c #FF6060",
+", c #FF6969",
+"' c #FF7272",
+") c #FF7B7B",
+"! c #FF8383",
+"~ c #FF8C8C",
+"{ c #FF9595",
+"] c #FF9E9E",
+"^ c #FFA7A7",
+"/ c #FFAFAF",
+"( c #FFB8B8",
+"_ c #FFC1C1",
+": c #FFCACA",
+"< c #FFD3D3",
+"[ c #FFDBDB",
+"} c #FFE4E4",
+"| c #FFEDED",
+"1 c #FFF6F6",
+"2 c #FFFFFF",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12"};
+
+
+// gamma correction green
+const char * gamma_g_xpm[] = {
+"30 20 31 1",
+" c None",
+". c #00FF00",
+"+ c #08FF08",
+"@ c #11FF11",
+"# c #1AFF1A",
+"$ c #23FF23",
+"% c #2BFF2B",
+"& c #34FF34",
+"* c #3DFF3D",
+"= c #46FF46",
+"- c #4FFF4F",
+"; c #57FF57",
+"> c #60FF60",
+", c #69FF69",
+"' c #72FF72",
+") c #7BFF7B",
+"! c #83FF83",
+"~ c #8CFF8C",
+"{ c #95FF95",
+"] c #9EFF9E",
+"^ c #A7FFA7",
+"/ c #AFFFAF",
+"( c #B8FFB8",
+"_ c #C1FFC1",
+": c #CAFFCA",
+"< c #D3FFD3",
+"[ c #DBFFDB",
+"} c #E4FFE4",
+"| c #EDFFED",
+"1 c #F6FFF6",
+"2 c #FFFFFF",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12"};
+
+
+// gamma correction blue
+const char * gamma_b_xpm[] = {
+"30 20 31 1",
+" c None",
+". c #0000FF",
+"+ c #0808FF",
+"@ c #1111FF",
+"# c #1A1AFF",
+"$ c #2323FF",
+"% c #2B2BFF",
+"& c #3434FF",
+"* c #3D3DFF",
+"= c #4646FF",
+"- c #4F4FFF",
+"; c #5757FF",
+"> c #6060FF",
+", c #6969FF",
+"' c #7272FF",
+") c #7B7BFF",
+"! c #8383FF",
+"~ c #8C8CFF",
+"{ c #9595FF",
+"] c #9E9EFF",
+"^ c #A7A7FF",
+"/ c #AFAFFF",
+"( c #B8B8FF",
+"_ c #C1C1FF",
+": c #CACAFF",
+"< c #D3D3FF",
+"[ c #DBDBFF",
+"} c #E4E4FF",
+"| c #EDEDFF",
+"1 c #F6F6FF",
+"2 c #FFFFFF",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12",
+".+@#$%&*=-;>,')!~{]^/(_:<[}|12"};
+
+// penguin
+const char * penguin_xpm[] = {
+"250 101 257 2",
+" c None",
+". c #000000",
+"+ c #010101",
+"@ c #020202",
+"# c #040404",
+"$ c #050505",
+"% c #060600",
+"& c #070707",
+"* c #080808",
+"= c #0A0A0A",
+"- c #0B0B0B",
+"; c #0D0D0D",
+"> c #0F0F0F",
+", c #101010",
+"' c #121212",
+") c #131313",
+"! c #151515",
+"~ c #171717",
+"{ c #181818",
+"] c #191918",
+"^ c #1C1C1C",
+"/ c #1D1D1D",
+"( c #212118",
+"_ c #21211C",
+": c #232307",
+"< c #222221",
+"[ c #232323",
+"} c #252525",
+"| c #272727",
+"1 c #292929",
+"2 c #2B2B2A",
+"3 c #2E2E22",
+"4 c #2D2D2D",
+"5 c #303004",
+"6 c #303005",
+"7 c #313107",
+"8 c #2F2F2D",
+"9 c #313131",
+"0 c #333333",
+"a c #383836",
+"b c #383838",
+"c c #3A3A3A",
+"d c #3F3F02",
+"e c #414108",
+"f c #3E3E3E",
+"g c #3F3F3F",
+"h c #424242",
+"i c #454545",
+"j c #464646",
+"k c #484848",
+"l c #4C4C10",
+"m c #4A4A4A",
+"n c #4E4E04",
+"o c #4D4D4D",
+"p c #535303",
+"q c #545409",
+"r c #525252",
+"s c #535353",
+"t c #57571E",
+"u c #585823",
+"v c #555555",
+"w c #565656",
+"x c #595958",
+"y c #595959",
+"z c #5A5A5A",
+"A c #5C5C5C",
+"B c #5D5D5D",
+"C c #62621A",
+"D c #5E5E5D",
+"E c #5F5F5F",
+"F c #616161",
+"G c #67671A",
+"H c #64645F",
+"I c #696908",
+"J c #696925",
+"K c #6A6A11",
+"L c #656565",
+"M c #68684A",
+"N c #666666",
+"O c #6A6A50",
+"P c #686868",
+"Q c #6D6D2F",
+"R c #696969",
+"S c #6B6B6B",
+"T c #6D6D6C",
+"U c #6D6D6D",
+"V c #6E6E6E",
+"W c #757500",
+"X c #6F6F6F",
+"Y c #787805",
+"Z c #727272",
+"` c #797906",
+" . c #747474",
+".. c #7B7B23",
+"+. c #787860",
+"@. c #767676",
+"#. c #787878",
+"$. c #7B7B65",
+"%. c #7A7A7A",
+"&. c #7B7B7B",
+"*. c #7D7D7D",
+"=. c #7E7E7E",
+"-. c #7F7F7F",
+";. c #818181",
+">. c #828282",
+",. c #8A8A02",
+"'. c #848484",
+"). c #858584",
+"!. c #868686",
+"~. c #8E8E01",
+"{. c #8D8C5A",
+"]. c #898989",
+"^. c #919101",
+"/. c #8B8B8B",
+"(. c #949401",
+"_. c #94941F",
+":. c #8D8D8D",
+"<. c #959500",
+"[. c #909076",
+"}. c #959535",
+"|. c #939450",
+"1. c #8F8F8F",
+"2. c #979718",
+"3. c #909090",
+"4. c #919191",
+"5. c #939393",
+"6. c #9B9B01",
+"7. c #9B9B0B",
+"8. c #949494",
+"9. c #9D9D01",
+"0. c #9E9E01",
+"a. c #969696",
+"b. c #9D9E3F",
+"c. c #989898",
+"d. c #A1A100",
+"e. c #999999",
+"f. c #A2A200",
+"g. c #9A9A9A",
+"h. c #9C9C92",
+"i. c #9C9C9C",
+"j. c #A5A501",
+"k. c #A2A264",
+"l. c #A1A172",
+"m. c #9D9D9D",
+"n. c #A6A600",
+"o. c #9F9F9F",
+"p. c #A8A800",
+"q. c #A8A925",
+"r. c #A1A1A1",
+"s. c #A2A2A2",
+"t. c #A5A682",
+"u. c #A5A5A4",
+"v. c #AEAE01",
+"w. c #A7A7A7",
+"x. c #AFAF68",
+"y. c #AAAAAA",
+"z. c #B5B503",
+"A. c #ADADAD",
+"B. c #B7B703",
+"C. c #B2B282",
+"D. c #B7B72A",
+"E. c #B0B0B0",
+"F. c #B1B1B1",
+"G. c #B2B2B2",
+"H. c #B6B68B",
+"I. c #B3B3B3",
+"J. c #BDBD01",
+"K. c #BEBE00",
+"L. c #B6B6B6",
+"M. c #BABA9D",
+"N. c #B8B8B8",
+"O. c #C2C20C",
+"P. c #BABABA",
+"Q. c #BBBBB3",
+"R. c #C5C502",
+"S. c #BCBCBC",
+"T. c #BDBDBD",
+"U. c #C4C47B",
+"V. c #CACA33",
+"W. c #C2C2C2",
+"X. c #CFCF01",
+"Y. c #C6C6C6",
+"Z. c #C9C9C9",
+"`. c #CBCBCB",
+" + c #CECECE",
+".+ c #DBDB02",
+"++ c #D0D0D0",
+"@+ c #D6D6B7",
+"#+ c #D4D4D4",
+"$+ c #E2E226",
+"%+ c #D7D7D7",
+"&+ c #E5E501",
+"*+ c #E2E271",
+"=+ c #DADADA",
+"-+ c #DCDCDC",
+";+ c #DDDDDD",
+">+ c #EEEE03",
+",+ c #E3E3E3",
+"'+ c #ECEC8D",
+")+ c #F2F20A",
+"!+ c #F3F314",
+"~+ c #F6F604",
+"{+ c #EBEBEA",
+"]+ c #F0F0E9",
+"^+ c #FEFE09",
+"/+ c #FFFF22",
+"(+ c #FFFF46",
+"_+ c #F9F9BA",
+":+ c #F4F4F4",
+"<+ c #FFFF7D",
+"[+ c #FAFAF9",
+"}+ c #FFFFFF",
+"|+ c #000000",
+"1+ c #000000",
+"2+ c #000000",
+"3+ c #000000",
+"4+ c #000000",
+"5+ c #000000",
+"6+ c #000000",
+"7+ c #000000",
+"8+ c #000000",
+"9+ c #000000",
+"0+ c #000000",
+"a+ c #000000",
+"b+ c #000000",
+"c+ c #000000",
+"d+ c #000000",
+"e+ c #000000",
+"f+ c #000000",
+"g+ c #000000",
+"h+ c #000000",
+"i+ c #000000",
+"j+ c #000000",
+"k+ c #000000",
+"l+ c #000000",
+"m+ c #000000",
+"n+ c #000000",
+"o+ c #000000",
+"p+ c #000000",
+"q+ c #000000",
+"r+ c #000000",
+"s+ c #000000",
+"t+ c #000000",
+"u+ c #000000",
+"v+ c #000000",
+"w+ c #000000",
+"x+ c #000000",
+"y+ c #000000",
+"z+ c #000000",
+"A+ c #000000",
+"B+ c #000000",
+"C+ c #000000",
+"D+ c #000000",
+"E+ c #000000",
+"F+ c #000000",
+"G+ c #000000",
+"H+ c #000000",
+"N . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .N ",
+" .Y.%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+Y. .",
+" .%+;+`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.;+%+ .",
+" .%+`.&. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .&.`.%+ .",
+" .%+`. .e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e. .`.%+ .",
+" .%+`. .g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g. .`.%+ .",
+" .%+`. .i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.y.y.A.A.A.A.A.A.A.E.E.E.E.F.F.F.G.G.I.Y.%+=+=+=+-+-+;+;+;+Z.m.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i. .`.%+ .",
+" .%+`. .m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.o.o.o.o.o.o.r.r.A.A.E.E.E.G.G.I.L.L.L.L.N.N.P.P.S.T.T.Z.Z.`.`.`. + + +++++++++#+#+#+%+%+%+%+%+%+%+=+=+=+=+-+;+;+`.o.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m. .`.%+ .",
+" .%+`. .o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.m.A.A.E.E.F.G.I.I.L.L.P.S.T.T.T.T.W.W.W.W.W.W.W.Y.Y.Y.Y.Y.Z.Z.Z.Z.`.`.`. + + + +++++++++#+#+#+#+%+%+%+%+%+=+=+=+=+-+;+`.m.m.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o. .`.%+ .",
+" .%+`. .r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.o.F.I.L.L.L.L.N.N.N.P.P.P.S.T.T.T.T.W.W.W.W.W.W.W.W.Y.Y.Y.Z.Z.Z.Z.Z.`.`.`. + + + +++++++++#+#+#+#+%+%+%+%+=+=+=+=+=+;+`.m.o.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r. .`.%+ .",
+" .%+`. .s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.o.F.I.L.L.L.L.N.N.N.N.P.P.S.T.T.T.T.T.W.W.W.W.W.W.W.Y.Y.Y.Y.Z.Z.Z.Z.`.`.`. + + + +++++++++++#+#+#+#+%+%+%+%+=+=+=+=+;+`.i.o.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s. .`.%+ .",
+" .%+`.@.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.o.E.I.I.L.L.L.L.N.N.N.P.P.P.S.S.T.T.T.W.W.W.W.W.W.W.W.Y.Y.Y.Y.Z.Z.Z.Z.`.`.`. + + + +I.y.A.A.A.A.A.E.E.E.E.F.F.=+=+=+;+Z.g.s.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.@.`.%+ .",
+" .%+`.@.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.o.E.I.I.I.I.A.y.w.u.u.u.w.w.w.w.w.e.E E F F F F F F F F F F F F H H H H H H L L L L | ! ! ~ h @.a.e.e.e.e.e.&.%+=+=+=+Z.e.u.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.@.`.%+ .",
+" .%+`.@.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.r.E.G.I.I.F.u.m.e.8.5.4.*.i < < < ] . . . . + . . . . . . . . + + + + + + + + @ + + + . . . . > j !.w.w.w.w.>.%+%+=+=+Y.a.u.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.@.`.%+ .",
+" .%+`.@.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.r.A.F.G.I.P.A.y.y.y.w.N ) . . . . . + = 9 o F o 1 $ + . + + + + @ @ @ @ ' 0 x P r 4 - @ @ + + . . $ s o.y.y.'.%+%+%+=+Y.a.w.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.Y.Y.Y.Y.W.T.T.T.T.G.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.@.`.%+ .",
+" .%+`.@.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.r.A.F.G.I.Y.E.y.y.].< + . . . . $ i /.Z.;+{+{+]+]+Z.X ; + + + @ @ @ ^ ].=+]+]+]+{+,+Z.1.h $ @ + . . . { Z y.'.%+%+%+=+W.8.w.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.P.W.W.W.T.S.P.P.P.N.N.N.A.A.A.A.y.y.Z.Z.P.P.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.@.`.%+ .",
+" .%+`.@.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.r.A.E.F.G. +I.w.R - . . . . $ h o.#+{+:+[+[+}+}+}+}+[+P.4 @ @ # * i ++[+}+}+}+}+}+[+[+]+;+o.0 & @ + + . * o =.%+%+%+=+W.8.w.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.L.T.Y.Y.W.E.o.m.a.5.4.4.1.:./.].].!.!.'.>.>.!.).'.g.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.@.`.%+ .",
+" .%+`.@.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.s.A.E.E.F. +y.j * . . . . ! V `.,+:+[+}+}+}+}+}+}+}+}+}+Y./ & = h =+}+}+}+}+}+}+}+}+}+}+[+]+++Z , # @ @ + # i #+%+%+%+W.4.y.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.N.T.++Z.w.w.i.c.a.8.8.4.3.1.:././.].!.!.).>.;.-.-.*.&.%.#.&.y.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.@.`.%+ .",
+" .%+`.@.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.s.y.E.E.F.`.m @ . . . . _ 8.%+{+[+}+}+}+}+}+}+}+}+}+}+}+[+1.> _ W.}+}+}+}+}+}+}+}+}+}+}+}+}+[+,+1._ & $ @ + b #+#+%+%+W.4.A.E.P.++%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+++Y.I.G.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.F.L.W.`.Y.A.A.e.e.c.a.8.5.4.3.1.:./.].].!.!.'.>.;.-.=.&.&.%.#.@. .5.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.@.`.%+ .",
+" .%+`.@.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.s.y.A.E.E.`.] . . . . ^ m.-+]+[+}+}+}+}+}+}+}+}+}+}+}+}+}+:+| F :+}+}+}+}+}+}+}+}+}+}+}+}+}+}+[+{+y.~ = & # c #+#+#+%+T.1.A.G.P.Z.F.r.g.g.g.g.e.`.-+-+-+;+;+;+;+#+G.L.T.I.F.A.F.F.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.I.Z. +%+L.y.e.e.e.c.a.8.5.4.3.1.:./.].].].!.'.>.;.-.-.*.&.%.#.@.@. .&.T.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.@.`.%+ .",
+" .%+`.@.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.s.y.A.E.E.`.] . . + * a.=+]+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+U L.}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+[+]+].! - * c #+#+#+%+T.:.A.I.I.W.%+T.w.u.W.;+L.W.=+=+=+-+-+;+;+#+G.G.E.y.s.m.a.1.a.4.E.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.L.Y.;+W.L.e.e.e.e.c.a.8.8.4.3.1.:././.].!.!.).>.;.-.=.*.&.%.#.@.@. .Z .L.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.@.`.%+ .",
+" .%+`.@.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.u.y.A.A.E.`.] + + + X ++{+[+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+m.=+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+[+,+N , - f #+#+#+%+T.:.E.L.A.Y.#+#+#+Y.w.=+I.W.=+=+=+=+=+=+-+#+G.G.A.w.s.m.a.4.].'.8.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.N.#+;+I.g.e.e.e.c.a.8.5.4.3.1.:./.].].!.!.'.>.;.-.=.&.&.%.#.@. .Z Z Z X c.W.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.@.`.%+ .",
+" .%+`.#.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.u.y.A.A.A.`.] + + 9 W.,+[+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+Z.{+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+:+Z.0 ; f ++#+#+#+P./.E.L.w.Z.%+=+=+ +u.%+G.T.%+%+=+=+=+=+=+#+G.F.A.w.s.i.a.4./.!.5.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.N.++ +w.e.e.e.c.a.8.5.4.3.:.:./.].].!.!.).>.;.-.-.*.&.%.#.@.@. .Z Z X V ;.W.N.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.#.`.%+ .",
+" .%+`.#.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.u.w.A.A.A.Z.^ + - ].%+]+}+}+}+}+}+[+[+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+[+[+}+}+}+}+}+}+[+{+]., f ++++#+#+P.].G.N.s. +=+%+%+`.u.#+E.T.%+%+%+%+%+%+=+#+G.F.A.w.r.g.a.4.].).5.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.Y.#+o.e.e.c.a.8.5.4.3.:.:./.].].!.).'.>.;.-.=.&.&.%.#.@. . .Z Z X U T X I.W.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.#.`.%+ .",
+" .%+`.#.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.u.w.y.A.A.Z.^ + 8 Z.,+:+}+}+}+}+[+5.E ++}+}+}+}+}+}+}+}+}+}+%+:+}+}+}+}+}+}+}+}+}+}+s.x Y.}+}+}+}+}+}+:+Z.9 f ++++++#+N.].I.P.i.++;+%+#+Z.s.#+E.S.#+%+%+%+%+%+%+#+G.E.y.u.o.g.8.3.].'.5.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.=+N.c.a.a.8.5.4.3.1.:./.].].].!.'.>.;.-.=.*.&.#.#.@. . .Z Z X V T S R i.Y.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.#.`.%+ .",
+" .%+`.#.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.u.w.y.A.A.Z.^ @ V ++{+[+}+}+}+}+8.# . Z }+}+}+}+}+}+}+}+}+}+4.=+}+}+}+}+}+}+}+}+}+:+c . ! {+}+}+}+}+}+[+,+P g ++++++#+N.!.I.P.a.#+-+#+++Y.r.++A.S.#+#+#+%+%+%+%+#+G.E.y.u.o.e.8.1.].'.5.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.Z.++o.a.8.5.4.3.:.:./.].].!.).).>.;.-.=.*.&.%.#.@.@. .Z Z X V T S R P %.`.T.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.#.`.%+ .",
+" .%+`.#.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.w.w.y.y.A.Z.^ ) s.%+{+[+}+}+}+}+Z . * w.}+}+}+}+}+}+}+}+}+}+ .W.}+}+}+}+}+}+}+}+}+}+ .& @ Z.}+}+}+}+}+[+{+i.g ++++++#+L.!.L.T.4.=+=+++ +W.o. +A.P.#+#+#+#+#+#+#+#+G.E.y.s.m.c.5.:.].>.5.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.-+L.8.5.4.3.:.:./.].].!.).'.>.;.-.=.&.&.%.#.@. .Z Z Z X U T S R N N R S.W.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.#.`.%+ .",
+" .%+`.#.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.w.u.y.y.y.Z.^ 1 W.=+{+[+}+}+}+}+=+>.s.:+}+}+}+}+}+}+}+}+}+[+z '.[+}+}+}+}+}+}+}+}+}+,+5.].[+}+}+}+}+}+}+{+W.k +++++++L.'.N.T.1.;+=+++`.W.m.`.y.N.++++++#+#+#+#+#+G.A.y.s.m.a.4.:.!.>.5.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T. +#+e.4.3.:.:./.].].!.!.'.>.;.-.=.*.&.#.#.@. . .Z X X V T S R P N L L i.Z.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.#.`.%+ .",
+" .%+`.#.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.w.u.y.y.y.Z.^ 0 `.;+{+[+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+Z.r s ,+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+]+++z + +++++I.>.N.W.3.;+%+ +Z.T.m.Z.w.N.++++++++++++#+#+F.A.w.s.m.a.4./.!.;.5.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.=+L.3.:.:./.].].!.).>.>.;.-.=.&.&.#.#.@. . .Z X X U T S R N N L H F =.Y.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.#.`.%+ .",
+" .%+`.#.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.w.u.w.y.y.Z.^ a ++;+{+[+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+[+/.w o L.}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+]+++N + + +++G.;.P.W.3.;+%+Z.Y.S.g.Y.w.N. + +++++++++++#+F.A.w.r.i.a.3./.).-.5.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W. +-+1.:./.].].!.!.'.>.;.-.=.*.&.#.#.@. .Z Z Z X V T R R P N L H F E S P.Y.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.#.`.%+ .",
+" .%+`.#.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.w.s.w.y.y.Z.^ a ++;+{+[+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+++s A E x {+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+[+{+++L + + +++G.;.P.W.3.;+#+Z.W.P.g.W.u.L. + + + +++++++#+E.y.w.o.g.8.3./.).-.5.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.Z.-+F./.].].!.).'.>.;.-.=.*.&.%.#.@. . .Z X X V T S R N N L H F E E B r.Z.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.W.#.`.%+ .",
+" .%+`.#.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.w.s.w.w.y.Z.^ 0 ++;+{+:+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+]+#.y E N D o.}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+[+{+Z.s + + + +E.-.S.Y.4.;+#+Y.W.L.e.W.s.L. + + + + + + +#+E.y.u.o.g.8.1.].'.-.5.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.%+#+/.].!.).>.>.;.-.=.&.%.#.#.@. .Z Z X X U S S R N N L H F E D A A !.Z.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.#.`.%+ .",
+" .%+`.#.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.w.s.w.w.w.Z.^ | W.;+{+:+[+}+}+}+}+}+}+}+}+}+}+}+}+}+[+m.x B L R U N Z.}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+[+{+E.h `. + + +E.=.T.Y.4.;+++W.T.I.a.T.r.L.`.`.`.`. + + +#+E.y.u.m.e.5.:.].>.=.5.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.`.=+A.!.!.'.>.;.-.=.*.&.#.#.@. .Z Z X X V T R R P N L H F E E B z z L W.`.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.#.`.%+ .",
+" .%+`.%.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.y.r.w.w.w.Y./ , g.;+,+{+[+}+}+}+}+}+}+}+}+}+}+}+}+}+Z.O O ..,.{.{.$.$.{+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+:+,+&.h `.`. + +A.&.W.Z.4.;+++W.P.G.a.S.o.I.Z.Z.`.`.`.`.`.#+A.y.s.m.c.4.:.].>.*.5.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.#+ +:.>.>.;.-.*.&.&.#.#.@. .Z Z X V U T R R N L L H F E D B z z x w y.`.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.%.`.%+ .",
+" .%+`.%.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.y.r.u.w.w.Y./ # k =+,+{+:+}+}+}+}+}+}+}+}+}+}+[+_+*+z.J.X.X..+.+.+X.O.V.'+]+[+}+}+}+}+}+}+}+}+}+}+}+[+{+Y.b h `.`.`. +A.&.W.`.4.-+ +T.N.E.a.N.m.G.Z.Z.Z.Z.Z.Z.`.#+A.w.s.i.a.4./.!.;.&.5.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`. +-+y.>.-.-.=.&.&.#.#.@. .Z Z Z X U T R R P N L H F E D B z z y w w %.++`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.%.`.%+ .",
+" .%+`.%.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.y.o.u.w.w.Y./ # ; /.,+{+]+[+}+}+}+}+}+}+_+'+$+.+.+.+.+$+$+&+>+>+>+>+&+&+$+$+$+*+'+[+}+}+}+}+}+}+}+}+:+,+R ! h Z.`.`. +y.%.W.`.5.-+ +P.L.A.e.L.m.G.Z.Z.Z.Z.Z.Z.Z.++A.w.s.i.a.4./.!.;.&.8.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.%+ +/.-.*.*.&.#.#.@. .Z Z X V V T R R N N L H F E E B z z x w w v E Y.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.%.`.%+ .",
+" .%+`.%. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +y.o.u.u.w.Y./ # $ ] s.,+{+:+[+}+[+_+*+.+.+.+$+&+&+&+>+>+)+~+~+^+^+~+~+~+!+)+>+>+&+&+&+'+_+[+}+}+}+[+,+=._ ! h Z.Z.`.`.y.#.W. +5.=+ +N.G.A.i.I.g.F.Y.Y.Y.Z.Z.Z.Z.++y.w.r.g.a.3./.!.-.&.5. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +++;+y.*.&.%.#.#.@. .Z Z X V U S R R N D s m m f c h k j r s y v v A y.++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +%.`.%+ .",
+" .%+`.%. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +y.o.u.u.u.Y./ $ $ * ~ L P.;+]+'+.+.+$+&+&+>+>+)+!+~+~+^+^+^+/+/+/+/+/+^+^+^+^+^+~+~+!+>+>+&+'+]+,+L.r [ ] ) g Z.Z.`.`.w.@.W. +5.=+`.L.E.y.o.G.e.E.Y.Y.Y.Y.Y.Y.Y.++y.u.o.g.8.1.].).-.%.5. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +%+;+!.&.#.@.@. .Z Z X X U T R P N w 8 ) = @ . . . . + $ - ' / c m =.`. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +%.`.%+ .",
+" .%+`.&.++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++y.m.s.u.u.Y./ $ & * - > } M X..+&+>+>+!+~+~+^+^+^+^+/+/+/+/+/+/+/+/+/+/+/+/+/+/+/+/+^+^+^+~+>+R.M 0 } / ] ) g Z.Z.Z.Z.w. .Y.++8.=+Z.I.A.y.s.E.c.E.W.W.W.Y.Y.Y.Y.++y.u.o.e.8.:.].'.=.#.5.++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++;+T.&.@.@. .Z Z X V U T R P N A 4 ; + . . . . . . . . . . . . . - 2 L w.`.++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++&.`.%+ .",
+" .%+`.&.#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+y.m.s.s.u.Y./ $ & * - > l K.&+>+~+~+^+^+/+/+/+/+/+/+/+/+/+/+/+(+(+(+(+/+/+/+/+/+/+/+/+/+/+/+^+~+O.e } / ] ) g Z.Z.Z.Z.w. .Z.#+8.=+Z.F.y.w.u.A.e.E.W.W.W.W.W.W.Y.++w.s.m.c.5.:.].>.*.#.5.#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+%+;+:.@. .Z Z X X U T R R N N w ' . + + + + + + + + . . . . . . . . . . - w y.++#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+&.`.%+ .",
+" .%+`.&.#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+A.m.s.s.u.Y./ $ & * - ( v.&+)+~+^+/+/+/+/+/+/+/+/+/+/+/+(+(+<+_+_+<+(+(+(+(+/+/+/+/+/+/+/+/+/+/+~+2.} / ] ) g Y.Z.Z.Z.u.Z Z.#+8.=+Y.A.w.w.w.y.g.E.W.W.W.W.W.W.W.++w.s.m.c.5.:.].>.*.@.5.#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+=+W. .Z Z X V U T R P N N H i > . . + + @ @ @ @ + + + + . . . . . . . . . . * F W.#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+&.`.%+ .",
+" .%+`.&.%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+A.i.r.s.s.W./ $ & = ; 6 R.&+>+~+~+^+^+/+/+/+/+/+/+/+/+(+(+<+<+<+(+(+/+/+/+/+/+/+/+/+/+/+/+/+^+^+!+v.[ / ] ) g Y.Y.Z.Z.u.Z Z.%+8.%+Y.y.u.w.y.w.g.F.W.W.W.W.W.W.W.++w.s.m.a.4./.!.>.&.@.8.%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+=+%+1.Z X V U S R P N L H H g & . + + @ @ @ @ # # @ @ @ @ + + + . . . . . . . . @ 1 m.%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+&.`.%+ .",
+" .%+`.&.%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+A.g.r.r.s.W./ $ * = ; ~ ~..+&+&+>+>+)+!+~+~+^+^+^+^+^+/+/+(+(+/+/+/+/+/+^+^+^+^+^+^+^+~+~+!+>+&+X.G [ / { ) g Y.Y.Z.Z.s.X `.%+8.%+W.y.r.u.A.u.i.I.T.T.W.W.W.W.W.++w.r.i.a.4./.!.;.&.@.5.%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+-+W.X X U T R P N N L F F v > . + @ @ @ # $ $ $ $ $ $ # # @ @ @ + + + . . . . . . . ; @. +%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+&.`.%+ .",
+" .%+`.&.=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+A.g.o.r.s.W./ $ * - ; ' 7 2.X..+.+$+$+$+&+&+&+&+>+>+>+)+~+~+^+^+~+~+~+!+)+)+>+>+>+&+&+$+$+.+X.B.Y 8 [ / { ' g Y.Y.Y.Y.r.V `.=+a.%+W.w.m.s.E.s.m.I.T.T.T.T.T.T.W. +u.o.g.8.3.].).-.%. .5.=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+:.T S R P N L L F F E o , . + @ @ @ # $ $ * * * * * & & $ $ @ @ @ + + . . . . . . . * E W.%+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+&.`.%+ .",
+" .%+`.&.=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+A.g.o.o.r.W./ $ * - > ) { } K v.V.X.X..+.+.+.+.+.+.+.+.+.+$+&+&+&+$+$+.+.+.+.+X.X.X.X.V.O.z.(.u 0 4 [ / { ' g Y.Y.Y.Y.o.V +=+a.%+W.u.g.r.I.o.m.I.S.T.T.T.T.T.T. +u.o.g.8.1.].'.-.#. .5.=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+;+W.S R R N L L F F E D w ! . + @ @ @ # $ & * = = - - - ; - = * $ # @ @ + + . . . . . . . @ b F.=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+&.`.%+ .",
+" .%+`.&.-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+A.g.o.o.r.W./ $ * - > ) ] / } 8 G q.V.V.V.X.X.X.X.X.X.X.X.X.X.X.X.X.V.R.R.R.O.K.J.B.D.D.~.u h c 0 2 [ / { ' g W.Y.Y.Y.m.U +-+a.%+W.Z.W.Y.L.m.m.I.P.S.S.S.T.T.T. +u.o.g.8.1.].>.=.#.Z 5.-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+;+'.P N N L F F E D B w 2 + . + @ @ # # $ * = ; ; , ' ) ) ) , ; = $ # @ @ + . . . . . . . . . ) u.-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+&.`.%+ .",
+" .%+`.&.;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+A.e.m.o.o.W./ $ * ; , ! ^ < 1 v y.,+{+,+%+@+U.U.U.R.R.R.R.R.O.J.J.B.D.D.x.x.x.H.Y.%+{+[+,+y.r b 9 2 [ / ~ , g W.W.Y.Y.m.U ++;+a.%+S.T.T.W.T.G.y.L.P.P.P.P.P.S.S. +s.m.e.5.:.].>.=.@.Z 5.;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+N.P L H F F E B A z y 0 + + + + @ # $ $ * - ; ' ! { ^ / / / ] ! , - * $ @ @ + + . . . . . . . . , P.;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+;+&.`.%+ .",
+" .%+`.%.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.o.c.m.m.o.W./ & = ; ) ] / 0 1.;+:+:+:+:+[+[+:+{+-+++Y.Q.M.H.H.C.C.y.A.I.W.#+,+:+}+}+}+}+}+}+%+#.b 2 [ ^ ~ , j W.W.Y.W.g.T P.Y.1.P.T.T.W.W.W.S.N.N.N.N.N.P.P.P.P. +s.m.c.4.:.!.>.*.@.Z ].Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.++;+*.L F F E D A z y x h * . + + @ @ $ $ * - > ) ] / < } | | | [ / { , - * $ @ @ + + . . . . . . . . h T.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.%.`.%+ .",
+" .%+`.#.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.g.a.m.m.o.W./ & - > ) ^ f L.:+:+:+:+:+[+[+}+}+}+}+[+:+]+,+ +`.Z.Z.;+{+:+[+}+}+}+}+}+}+}+}+}+}+:+4.9 < ^ ! , r W.W.W.W.g.S L.T./.I.I.L.W.1.!.).>.y.L.N.N.N.N.N.N. +r.i.a.4./.!.;.&.@.Z ].T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.W.=+L.N E E B A z y w w v ] . + + @ @ # $ & = > ! ^ < } 1 4 8 9 9 4 | < ] ' - * $ @ @ + . . . . . . . . # -.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.#.`.%+ .",
+" .%+`.#.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.g.a.i.m.m.W./ & - > ) | L.}+}+}+[+[+:+[+}+}+}+}+}+}+}+}+[+[+[+[+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+[+>./ ] ) > D W.W.W.W.c.R I.T./.I.I.I.%+S.u.e.].u.L.L.L.L.L.N.N. +o.g.a.3./.).-.&. .X !.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.`.;+&.E D A z z x w v v 0 @ . + + @ @ $ $ * ; ! ^ < | 4 9 0 b b b a 9 2 < ] ' - * $ # @ + . . . . . . . . | w.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.T.#.`.%+ .",
+" .%+`.#.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.e.a.i.m.m.W./ & = ; ) -.[+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+}+:+f ! , - R W.W.W.W.c.R G.S./.G.G.I.=+W.y.e.].s.L.L.L.L.L.L.L. +o.g.a.3.].'.-.%. .X !.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.W.%+P.D A z y w w w @.-.&.) . + + @ @ # $ * - ) ] < | 4 0 b c g h h g c 0 4 < ] ' ; * $ @ @ + . . . . . . . + o L.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.#.`.%+ .",
+" .%+`.#.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.a.8.g.i.m.W.X F H L R -+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+[+P.R N F g.W.W.W.W.a.R F.P.].F.F.F.=+Y.A.g.].s.I.I.I.L.L.L.L. +o.g.8.1.].>.=.#.Z V '.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.Z.%+=.z y Z #.5.w.w.y.y.y . . + + @ @ # $ * > ~ / [ 2 9 a f h i k k k i f a 2 < { ' - * $ @ + + . . . . . . . , 4.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.#.`.%+ .",
+" .%+`.#.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.a.8.g.g.i.s.y.y.A.A.A.A.A.E.E.E.E.F.G.G.I.I.I.L.L.L.L.L.N.N.N.N.P.P.P.S.S.T.T.T.T.T.W.W.W.W.W.W.W.W.W.Y.Y.Y.W.W.W.W.W.8.P E.N.].E.E.E.=+Z.I.m.].r.G.I.I.I.I.I.I.`.m.e.8.:.].>.*.#.Z U >.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.S.#+Y.:.s.s.A.E.E.A.A.A.o._ . . + + @ @ # & - ' ] _ | 4 0 c h j m o r o o j f 0 2 _ { , - * $ @ + + . . . . . . + | -.m.L.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.#.`.%+ .",
+" .%+`.#.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.8.8.g.g.i.i.m.m.m.o.o.o.r.s.s.s.u.u.u.w.w.w.w.y.y.y.y.A.A.A.A.A.E.E.F.F.G.I.I.I.L.L.L.L.N.N.N.P.P.P.S.T.T.T.T.T.W.W.W.8.N A.L.].A.A.A.=+ +N.o.].o.F.F.G.G.G.I.I.`.m.c.5.:.].>.*.@.Z T >.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.G.N.I.I.I.G.G.F.F.E.E.E.).$ . + + + @ @ $ * ; ) ^ < | 4 0 f i m o s w w s o j c 9 | / ~ , - & # @ + + . . . . . . . $ | o &.y.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.#.`.%+ .",
+" .%+`.@.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.4.8.e.g.g.g.i.m.m.m.o.o.o.r.s.s.s.u.u.u.w.w.w.w.y.y.y.y.A.A.A.A.E.E.E.E.F.G.I.I.I.L.L.L.L.N.N.N.P.P.P.S.T.T.T.T.T.W.W.4.N y.I.!.A.A.A.-+#+S.s.].o.E.E.E.F.F.F.G.`.i.a.4./.!.;.&.@.Z S -.I.I.I.I.I.I.I.I.I.I.I.I.I.L.L.I.I.I.I.I.I.I.I.I.y.i.I.L.L.I.I.I.I.G.G.E.o . . + + @ @ @ $ * ; ) ^ < 1 8 a g j o r w z A z v o h b 4 } ^ ! ; = & # @ + . . . . . . . . / } , y I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.@.`.%+ .",
+" .%+`.@.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.3.5.e.e.g.g.g.i.m.m.m.o.o.o.r.s.s.s.u.u.u.w.w.w.w.y.y.y.y.A.A.A.A.E.E.E.F.F.G.I.I.I.L.L.L.L.N.N.N.P.P.P.S.T.T.T.T.W.T.4.N y.F.!.y.y.A.-+%+W.w.].m.A.E.E.E.E.E.E.`.g.a.4./.!.-.&.@.X R -.F.F.F.F.F.F.F.F.F.F.F.F.Y.++ +++F.F.F.F.F.F.F.F.y.e.w.N.L.L.L.L.L.I.I.w.1 . + + + @ @ @ $ * ; ) ] < | 8 a g j o v A E F E z s k f 0 2 < ] ) ; * $ # @ + . . . . . . . * 0 m :.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.@.`.%+ .",
+" .%+`.@.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.1.;.!.!.!.!.].]././.8.o.o.o.r.r.s.s.u.u.u.w.w.w.w.w.y.y.y.A.A.A.A.A.E.E.E.F.G.I.I.I.L.L.L.L.L.N.N.P.P.P.S.S.T.T.T.T.T.3.N w.E.!.y.y.y.=+=+Y.A.].m.A.A.A.A.E.E.E.`.g.8.3.].).-.%. .X R -.E.E.E.E.E.E.E.E.E.E.L.++w.!.!.c.++++Z.Z.Z.Z.Z.Z.N.e.e.w.P.L.L.L.L.L.I.o._ + + + + @ @ @ $ = ; ) ] _ | 4 a g j o w B F L L E x o h a 4 } / ~ ' ; * $ @ + + . . . . . . = 1.A.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.@.`.%+ .",
+" .%+`.@.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.y.s.s.s.s.s.s.s.u.u.c.]././.:.:.1.3.4.4.4.5.3.>.>.>.>.'.).!.s.s.u.u.w.w.w.w.y.y.y.A.A.A.E.E.F.F.G.L.N.P.P.S.T.T.T.T.T.1.N w.A.).y.y.y.=+-+`.F./.i.A.A.A.A.A.A.A.Z.g.8.3.].'.=.#. .V R =.A.A.A.A.A.A.A.A.A.A.s.5.S.G.y.g.>.>.a.a.a.a.a.a.a.e.g.i.F.P.N.N.N.F.A.4.! + + @ @ @ @ @ $ * ; ) { / } 4 0 f j o w D L N N H z r h b 4 | _ ] ) > = $ # @ + . . . . . . . z A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.@.`.%+ .",
+" .%+`.@.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.m.#.@.@. .Z Z X #.#.#.@.@.@. . . . . . .@.@.@.@.@.m.y.y.A.A.A.E.E.F.G.I.I.5.>.y.A.).w.w.w.=+-+ +L.1.g.y.y.y.A.A.A.A.Z.e.8.1.].>.*.#.Z U P *.A.A.A.A.A.A.A.A.A.A.w.!.8.L.Y.L.A.A.e.e.e.e.e.e.e.g.m.m.s.P.P.P.P.N.A.5.] @ @ @ @ @ @ @ $ * - , ! / [ 2 0 c i o w E L R R N D r i b 4 } _ ] ) , - * $ @ + + . . . . . . } m.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.@.`.%+ .",
+" .%+`.@.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.w.w.w.w.w.w.w.w.y.y.y.y.y.y.y.y.y.y.y.y.y.y.e.4.4.3.:././.].].!.).>.>.;.-.*.&.%.#.@.@. .Z Z X m.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.'.w.w.w.=+;+#+P.4.g.y.y.y.y.y.y.y.Z.c.5.:.].>.&.@.Z T N *.y.y.y.y.y.y.y.y.y.y.y.y.i.!.1.P.N.y.y.y.y.y.y.y.u.m.o.o.r.w.S.S.S.P.G.g.[ # # # # @ @ @ $ * - > ) ^ < | 9 c h o v D L R S N E r h a 2 [ / { ) > - * $ # @ + + . . . . . @ Z y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.y.@.`.%+ .",
+" .%+`.@.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.u.].&.&.&.&.&.&.&.>.].].].].].].].].].].].].].].r.w.w.w.w.u.u.u.s.s.r.o.m.m.g.e.c.a.8.8.4.3.1.:./.s.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.>.u.u.u.=+;+%+W.a.e.w.w.w.y.y.y.y.Z.a.4.:.!.>.&.@.Z S N ].`.`.`.`.`.`.Z.w.w.w.w.w.w.w.m.).w.w.w.w.w.w.w.w.:.1.r.s.u.u.E.W.T.T.T.A.a & & $ $ $ $ # # $ = ; ' { / } 4 a g m s B L R R N B o h 9 } / { ) , ; = & $ # @ + + . . . . . . i w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.@.`.%+ .",
+" .%+`.@.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.1.*.:.3.].T @.3.3.>.P ;.#.Z /.:.>.*.:.:.:./.*.*./.-.&.&.&.&.&.&.&.&.&.&.&.&.&.&.].1.1.1.1.:./././.].:.:.:.:.a.w.w.w.w.w.w.w.w.w.w.w.>.u.u.u.=+;+=+W.e.e.w.w.w.w.y.y.y.Z.a.4./.!.;.&.@.Z S N Z !.!.!.!.!.!.:.Y.Y.Y.Y.Y.W.W.Y.G.1.Y.w.w.w.w.w.w.].h s.u.u.w.y.N.W.T.T.T.R - - * & $ $ $ # $ * - , ! ^ < 2 0 f j r z H P R L z o c 4 < { ' > - * & $ $ # @ + + . . . . . . ] 8.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.w.@.`.%+ .",
+" .%+`.@.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.5.-.].3.3.-. .X .&.Z Z @.:.!.S >.]./.].3.3./.#.Z *.1.!.).#.U /.!.'.].3.3.!.'.].3./.>.].].].].].]./././././././.!.-.-.-.-.-.-.-.m.u.u.u.>.s.s.s.=+;+-+Z.g.a.w.w.w.w.w.w.w.Z.8.3./.).-.%. .X R N #.g.g.g.g.g.g.c.-.-.-.-.>.8.8.:.).r.y.u.u.u.u.u.u.u.o L w.w.y.y.A.S.W.W.T.s.^ , ; - * * * & $ & = ; ) ] _ | 9 c i o x F L N F x k a | ^ ' - * $ # # @ @ @ @ + + . . . . . . - =.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.@.`.%+ .",
+" .%+`. .s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.i.].).1.3.3.3.&.!.].-.&.].].1.4.>.@.#.!.:././.].&.&.%.].4.'.Z >.].4.!. .1.8.8.%.Z ).8.!.S ).5.a.8.#.Z !.e.e.e.-.>.3.e.4.a.a.4.g.i.c.E s.s.s.;.r.s.s.=+;+;+ +o.a.u.u.u.u.w.w.w.Z.8.1.].'.-.#. .X R N %.s.s.s.s.s.s.s.s.s.s.s.r.5.5.c.s.s.s.s.s.s.s.s.s.s.g.b @.y.A.A.E.F.Z.W.W.N.j ] ) > ; - = * * & * - , ! ^ [ 4 a g m s A F H D s h 9 < ) - $ @ @ @ @ + + + + + + . . . . . . @ X s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s. .`.%+ .",
+" .%+`. .r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.o.!.>./.5.3.:.4.5.:.5.5.8.8.8.8.8.a.4.4.a.c.!.-.!.3.>.1.1.g.8.'.&.&.i.i.a.).).4.m.i.o.o.o.g.1.=.:.r.g.1.1.g.s.s.s.>.X =.s.#.4.g.3.s.r.e.z r.r.r.-.o.o.r.=+;+;+++s.8.s.s.w.r.o.s.u.Z.8.1.].>.=.#.Z V P N %.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r. .0 w.A.E.F.I.N.Z.W.W.o.} ^ ) , ; - - = * * = ; ) ] _ 1 9 c i o x B B w o c 1 ] - @ @ . . . . . . . + + + . , : @ . . * #.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r. .`.%+ .",
+" .%+`. .o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.5.>.8.i.m.e.5.g.m.4.a.4.1.a.g.5.:.1.m.s.s.s.o.:.3.a.s.r.a.a.i.o.a.r.w.w.w./.3.a.w.i.'.8.8.y.s.8.5.8.A.u.5.5.r.A.A.A.A.E.E.E.g.w.E.F.y.o./.s o.o.o.-.o.o.o.=+;+;+%+w.8.s.s.W./.&.1.u.Y.5.:.].>.*.@.Z U N N ].o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.#.0 w.G.I.L.L.S.Z.Y.W.s [ ^ ! ) , ; - * $ * - , ! ^ [ 4 a g k r w w o h 9 / ; # . . . . . . . . . . . . @ I v.n @ . , ;.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o.o. .`.%+ .",
+" .%+`. .m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.g.].4.s.u.u.w.u.u.w.y.y.y.s.w.y.u.r.y.y.y.c.c.g.e.a.o.u.w.8.g.m.w.A.w.w.u.y.G.F.G.I.I.A.i.i.y.L.A.g.g.y.L.w./.m.L.s.!.!.a.A.).'.E.P.P.I.u.4.&.o m.m.m.-.m.m.m.=+;+;+=+w.8.r.r.W.s.:.1.s.Y.4.:.].>.*.@.Z T &.g.i.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.9 &.I.L.L.N.N.Z.Z.Y.w.c [ ^ { ! , * @ # $ * ; , ~ / | 9 c h m o o j b } ) $ + . . . . . . . . . . . . 5 K.K.6.d @ [ a.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m. .`.%+ .",
+" .%+`. .g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.:.].w.F.A.A.E.y.A.F.F.y.w.y.w.w.w.E.F.F.L.L.I.I.L.L.L.N.N.N.L.N.P.P.I.y.A.u.E.s.m.s.T.T.I.s.I.I.W.L.T.W.W.I.F.A.I.S.I.w.S.W.w.:.N.W.Y.S.y.a.>.V m g.g.g.=.i.u.u. +%+#+++y.a.o.o.N.`.Z.A.s.W.4./.!.;.&.Z &.8.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.4.c &.L.N.P.S.T.`.Z.Y.).9 < / ~ * @ @ @ $ & - ; ) ^ < 2 9 c h i h c 4 ^ = + . . . . . . . . . . . . . W X.z.6.,.: f i.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g.g. .`.%+ .",
+" .%+`. .e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.3.!.s.P.P.P.S.S.S.N.T.T.T.P.P.T.F.N.P.T.E.E.N.E.E.E.F.P.T.T.W.W.T.T.W.T.T.W.Y.Y.Y.A.P.T.Z.Z.T.y.y.L.Z.u.W.`.`.m.o.w.w.I.w.w.L. +T.W. + +Y.F.m.].Z A S e.e.g.m.E.T.T.N.E.u.a.1.a.m.o.o.o.o.o.o.P.3./.!.=. .].a.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.:.9 3.P.S.T.W.W.++Z.W.R | ^ & . . + $ > * * - , ! ^ [ 2 0 b c b 9 < , # . . . . . . . . . . . . . # n.R.v.6.6.W +.A.g.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e.e. .`.%+ .",
+" .%+`. .a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.!.o.S.W.W.W.W.W.W.Y.Y.Y.Y.Z.Z.Z.Z.Z.Z.Z.Z.Z.`.Y.A.N.W.Y.I.A.A.Y.W.A.E.Z.y.A.L. +L.L.L.T. +L.L.W.#+++++#+Y.++#+%+Y.`.%+%+T.L.Y.#+#+w.=+=+ +I.u.1.@.B @.a.8.c.u.G.T.Z.`.W.N.y.g.:.].m.m.m.m.m.m.o.L.1.].%.*.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.E k N.T.W.W.W.`.++`.F.c & $ + . $ ) ! ' ; = - , ~ / [ 2 8 9 4 [ ! & + . . . . . . . . . . . . . ( O.J.j.6.6.<.b.L.E.G.c.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a. .`.%+ .",
+" .%+`. .8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.&./.:.1.4.8.4.5.g.G.I.I.L.L.L.N.Y.#+#+#+#+#+#+%+%+%+%+%+%+%+%+%+=+=+=+=+Y.%+=+=+Y.Y.Y.%+P.P.Y. +%+G.y.;+;+Z.W.W.++%+W.Y.,+W.W.-+ +W.,+#+L.y.8.-.o >.8.;.].e.w.L.W.#+%+Z.P.A.m.1.].g.i.i.i.m.m.m.A.&.=.:.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.H g W.W.W.Y.Z. +#+`.w & @ @ # ^ < ^ { ! , * - , ~ ^ < } } < { ; @ . . . . . . . . . . . . . . d R.z.d.6.<.6.7.t.N.N.s.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8. .`.%+ .",
+" .%+`. .8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.*.U X Z Z .#.&.=.;.'.]./.:.4.8.e.m.o.r.u.w.w.y.A.E.F.`. +++++++++#+#+#+%+%+,+,+,+,+{+{+,+{+{+{+=+y.`.{+{+ +Y.Y.{+G.W.=+++Z.++ +A.=+-+L.A.i. .o h >.&.#.].a.u.I.T.`. +Y.N.y.g.:.-.%.%.%.%.&.!.g.8.1.5.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.5.k g.W.Y.Z.Z.Z.++y.] # . * ] 0 4 } _ ^ ) $ * - , ! ] ^ ^ ~ > & @ . . . . . . . . . . . . . . K R.p.0.9.<.9.0.7.N.N.N.u.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8. .`.%+ .",
+" .%+`. .4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.Z w w w w w w w x x y y z A P X Z .@.#.&.-.>.).]./.1.5.a.e.i.o.s.u.w.y.A.I.W.W.Y.Y.Z.`.`. + + +=+;+,+{+{+{+{+{+{+,+{+{+,+{+]+,+,+,+L.E.r.&.z z z =.U @.).5.o.A.L.T.T.P.E.u.a.].>.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.].9 a.Z.Z.`.`. +'.! # , < f h c 0 4 } / ' * * - > ' ) ) > ; q 5 @ . . . . . + . + . . . . . 6.O.f.9.d.2.6.d.6.k.m.3.].s.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4. .`.%+ .",
+" .%+`.Z 3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.>.#.#.#.#.#.#.#.#.E r r r r r r s s B E F F H L P S U Z *.-.>.!.]./.1.4.a.e.i.o.r.s.u.E.L.L.N.P.P.S.T.T.T.Y.;+,+,+,+,+;+N.G.w.-.Z 3.3.3.!.P Z -./.c.s.y.E.F.A.w.i.3.=.H 3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.>.o W.`. + +++X a X :.h j o m i f 0 4 } / ; & * - - - * 7 R.6.d $ . . . . + . . + . . . > R.B.6.6.j.2.6.d.0.7.[.1.e.e.a.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.Z `.%+ .",
+" .%+`.Z :.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:. .U U U U U U U U F o o o o r r s s E U X Z @.#.&.-.>.!./.1.5.a.e.i.o.s.u.w.w.y.A.F.G.y.P Z :.:.:.:.).N S #.>.:.a.m.s.s.o.g.4.>.z !.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.o E Z.++++#+W.Y.;+++4.h m w v o j f 0 2 ! $ $ & & & ; ,.X.j.,.d @ . . . . . . . . . . 5 X.z.<.6.j.6.<.0.0.9.b.g.e.i.o.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.Z `.%+ .",
+" .%+`.Z /././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././.%.F F F F F F F F w o r r s w w x z Z -.>.!.].u.E.N ]./././././.].U N X #.>.].4.8.8.4.:.R s !./././././././././././././././././././././././././././././././././././././././././././././././././././././././.r x #+#+#+%+=+;+=+++8.g o D E z r j c 4 { $ @ @ @ 7 X.K.f.(.~.5 . . . . . . . . . . p V.p.^.6.n.6.(.6.0.0.7.l.g.e.e./././././././././././././././././././././.Z `.%+ .",
+" .%+`.Z /./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././.].].].].].].].].].R y y y y z z ]./././././././.].B m r v y A D D z r X ]././././././././././././././././././././././././././././././././././././././././././././././././././././././././.].i E.#+%+%+=+-+,+-+#+G.z o E N L z o g 2 * + @ + q .+z.f.(.6.,.: . . . . . . . . @ ..O.d.^.6.n.0.(.<.6.9.6.b.h.e.e.:././././././././././././././././././././.Z `.%+ .",
+" .%+`.Z ].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].;.%.%.%.%.%.%.%.*.].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].].@.9 L.=+=+-+;+;+,+=+%+W.#.o A N N z o c [ = . . ` X.p.d.<.6.0.,.! . . . . . . . % ~.J.2.~.0.n.d.(.^.2.7.6.7.l.e.e.4.].].].].].].].].].].].].].].].].].].].].Z `.%+ .",
+" .%+`.Z !.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.@.L %+;+;+;+;+;+,+=+%+++4.s s E F w i 8 ! $ @ ~.V.f.0.2.6.f.6.G - . . . . . . > n.v.^.~.d.n.d.(.^.t.l.k.b.l.g.e.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.Z `.%+ .",
+" .%+`.Z '.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.v #.=+;+;+;+;+;+,+=+=+%+I.L i r r k b [ ' : K.O.9.d.6.6.d.0.6.e @ . . . . ^ O R.j.~.~.d.n.f.(.}.s.r.o.m.i.e.3.>.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.'.Z `.%+ .",
+" .%+`.Z >.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.g Z ;+;+;+;+;+;+{+=+=+=+#+&.h b c a 1 { d X.B.<.d.0.<.6.0.0.~.: . . * g r.M.V.2.,.k.q.n.j.(.b.s.r.o.o.4.!.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.>.Z `.%+ .",
+" .%+`.Z ;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.-.h W.;+;+;+;+;+,+,+=+=+=+T.u.].j | / ~ q X.z.<.f.d.<.6.0.0.9.` : f g.L.L.U.K.~.|.y.t.v.j.(.l.s.s.8.).@.x ;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.;.Z `.%+ .",
+" .%+`.Z -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.N c W.;+;+;+;+;+,+,+;+-+-+-+=+`.A.s | C X.q.(.j.f.2.<.9.9.9.6.}.I.P.u.w.U.v.}.y.y.A.C.p.2.t.a.'.X j [ B -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.Z `.%+ .",
+" .%+`.Z *.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.L .;+;+;+;+;+;+{+;+;+;+;+;+,+;+++I.D.X.j.^.j.j.6.^.2.6.6.9.7.|.P.w.G.U.d.y.E.E.A.y.t.}.;.P h / { z *.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.Z `.%+ .",
+" .%+`.Z &.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.j /.=+;+;+;+;+,+{+,+]+{+,+=+%+#+#+*+V.9.^.j.j.6.^.^.6.6.6.6.2.Q.N.L.H.C.I.F.y.a.].Z T c ^ , ^ A &.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.Z `.%+ .",
+" .%+`.Z #.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.9 ].;+;+;+;+;+,+]+-+-+=+%+%+#+#+*+O.<.~.j.n.6.^.~.k.b.7.6.<.k.N.N.L.I.u.3.@.P E 0 ! , ~ k #.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.Z `.%+ .",
+" .%+`.Z #.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.@.A Z.;+;+;+;+;+{+;+-+=+%+%+#+#+.+B.^.,.j.n.0.^._.W.W.W.Q.M.M.N.G.a.=.N B h / ' , _ k @.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.Z `.%+ .",
+" .%+`.X @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.E i Y.;+;+;+;+;+;+;+=+=+%+#+#+X.v.~.~.j.n.d.^.b.W.W.W.T.T.s.#.S A m c / ) } 9 R U @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.X `.%+ .",
+" .%+`.X . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .v ].;+;+;+;+;+=+;+=+=+%+%+#+X.j.~.x.v.n.f.^.k.L.F.u.&. .z k c _ ~ } b A T . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .X `.%+ .",
+" .%+`.X Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z f a.;+;+;+;+#+-+-+=+%+%+%+V.6.b.Y.M.v.f.<.[.S F w k | } ! ] f r Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z Z X `.%+ .",
+" .%+`.X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X R | o.;+;+;+;+`.;+=+=+T.y.J._.!.-.&.l.q._.F h 4 ~ , , i s X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X `.%+ .",
+" .%+`.X V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V U z #+;+;+;+'.'.'.'.>.>.|.;.X N D o C u ) , / b z U V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V X `.%+ .",
+" .%+`.X T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T r r +;+;+>.>.;.=.@.X D k g ] ] ! ~ 4 m F L T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T X `.%+ .",
+" .%+`.X R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R j i.++A.Z H a | } [ ^ , ) [ [ r z R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R X `.%+ .",
+" .%+`.X P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P w i 0 [ , , , , ] ] g o P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P P X `.%+ .",
+" .%+`.X N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N 4 , , , ^ g g N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N X `.%+ .",
+" .%+`.X N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N L L L N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N X `.%+ .",
+" .%+`.%.X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X %.`.%+ .",
+" .%+;+`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.;+%+ .",
+" .Y.%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+%+Y. .",
+"N . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .N "};
diff --git a/frontend/xpm_data.h b/frontend/xpm_data.h
new file mode 100644
index 0000000..ac7186c
--- /dev/null
+++ b/frontend/xpm_data.h
@@ -0,0 +1,48 @@
+/*
+ SANE EPSON backend
+ Copyright (C) 2001 SEIKO EPSON CORPORATION
+
+ Date Author Reason
+ 06/01/2001 N.Sasaki New
+
+ This file is part of the `iscan' program.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the copyright holders give permission
+ to link the code of this program with the esmod library and
+ distribute linked combinations including the two. You must obey
+ the GNU General Public License in all respects for all of the
+ code used other then esmod.
+*/
+
+#ifndef ___XPM_DATA_H
+#define ___XPM_DATA_H
+
+extern char * scan_xpm [ ];
+extern char * preview_xpm [ ];
+extern char * zoom_xpm [ ];
+
+extern char * auto_xpm [ ];
+
+extern char * gamma_m_xpm [ ];
+extern char * gamma_r_xpm [ ];
+extern char * gamma_g_xpm [ ];
+extern char * gamma_b_xpm [ ];
+
+extern char * penguin_xpm [ ];
+
+#endif // ___XPM_DATA_H
+
diff --git a/gettext.h b/gettext.h
new file mode 100644
index 0000000..8b262f4
--- /dev/null
+++ b/gettext.h
@@ -0,0 +1,69 @@
+/* Convenience header for conditional use of GNU <libintl.h>.
+ Copyright (C) 1995-1998, 2000-2002 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published
+ by the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ USA. */
+
+#ifndef _LIBGETTEXT_H
+#define _LIBGETTEXT_H 1
+
+/* NLS can be disabled through the configure --disable-nls option. */
+#if ENABLE_NLS
+
+/* Get declarations of GNU message catalog functions. */
+# include <libintl.h>
+
+#else
+
+/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which
+ chokes if dcgettext is defined as a macro. So include it now, to make
+ later inclusions of <locale.h> a NOP. We don't include <libintl.h>
+ as well because people using "gettext.h" will not include <libintl.h>,
+ and also including <libintl.h> would fail on SunOS 4, whereas <locale.h>
+ is OK. */
+#if defined(__sun)
+# include <locale.h>
+#endif
+
+/* Disabled NLS.
+ The casts to 'const char *' serve the purpose of producing warnings
+ for invalid uses of the value returned from these functions.
+ On pre-ANSI systems without 'const', the config.h file is supposed to
+ contain "#define const". */
+# define gettext(Msgid) ((const char *) (Msgid))
+# define dgettext(Domainname, Msgid) ((const char *) (Msgid))
+# define dcgettext(Domainname, Msgid, Category) ((const char *) (Msgid))
+# define ngettext(Msgid1, Msgid2, N) \
+ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+# define dngettext(Domainname, Msgid1, Msgid2, N) \
+ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+# define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \
+ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+# define textdomain(Domainname) ((const char *) (Domainname))
+# define bindtextdomain(Domainname, Dirname) ((const char *) (Dirname))
+# define bind_textdomain_codeset(Domainname, Codeset) ((const char *) (Codeset))
+
+#endif
+
+/* A pseudo function call that serves as a marker for the automated
+ extraction of messages, but does not call gettext(). The run-time
+ translation is done at a different place in the code.
+ The argument, String, should be a literal string. Concatenated strings
+ and other string expressions won't work.
+ The macro's expansion is not parenthesized, so that it is suitable as
+ initializer for static 'char[]' or 'const char[]' variables. */
+#define gettext_noop(String) String
+
+#endif /* _LIBGETTEXT_H */
diff --git a/include/sane/sanei.h b/include/sane/sanei.h
new file mode 100644
index 0000000..ece1beb
--- /dev/null
+++ b/include/sane/sanei.h
@@ -0,0 +1,160 @@
+/* sane - Scanner Access Now Easy.
+ Copyright (C) 1996 David Mosberger-Tang and Andreas Beck
+ Copyright (C) 2002, 2003 Henning Meier-Geinitz
+
+ This file is part of the SANE package.
+
+ SANE is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ SANE is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+ License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with sane; see the file COPYING. If not, write to the Free
+ Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+*/
+
+/** @file sanei.h
+ * Convenience macros and function declarations for backends
+ * @sa sanei_backend.h sanei_thread.h
+ */
+
+/* Doxygen documentation */
+
+/** @mainpage SANEI (SANE internal routines) documentation
+ *
+ * @image html sane-logo2.jpg
+ * @section intro Introduction
+ *
+ * The header files in the include/sane/ directory named sanei_*.h provide
+ * function declarations and macros that can be used by every SANE backend.
+ * Their implementations can be found in the sanei/ directory. The code aims
+ * to be platform-independent to avoid lots of \#ifdef code in the backends.
+ * Please use the SANEI functions wherever possible.
+ *
+ * This documentation was created by the use of doxygen, the
+ * doc/doxygen-sanei.conf configuration file and documentation in the sanei_*.h
+ * files.
+ *
+ * This documentation is far from complete. Any help is appreciated.
+ *
+ * @section additional Additional documentation
+ * - The SANE standard can be found at <a
+ * href="http://www.sane-project.org/html/">the SANE webserver</a>,
+ * though the PostScript version produced from the source may be more recent.
+ * - Information on how to write a backend: <a
+ * href="../backend-writing.txt">backend-writing.txt</a>.
+ * - General SANE documentation is on <a
+ * href="http://www.sane-project.org/docs.html">the SANE documentation
+ * page</a>.
+ *
+ * @section contact Contact
+ *
+ * The common way to contact the developers of SANE is the sane-devel
+ * mailing list. See the <a
+ * href="http://www.sane-project.org/mailing-lists.html">mailing list webpage</a>
+ * for details. That's the place to ask questions, report bugs, or announce
+ * a new backend.
+ *
+ */
+
+#ifndef sanei_h
+#define sanei_h
+
+#include <sane/sane.h>
+
+/** @name Public macros and functions
+ * @{
+ */
+/** @def STRINGIFY(x)
+ * Turn parameter into string.
+ */
+/** @def PASTE(x,y)
+ * Concatenate parameters.
+ *
+ */
+/** @def NELEMS(a)
+ * Return number of elements of an array.
+ *
+ */
+
+/** @fn extern SANE_Status sanei_check_value (const SANE_Option_Descriptor * opt, void * value);
+ * Check the constraints of a SANE option.
+ *
+ * @param opt option to check
+ * @param value value of the option
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on success
+ * - SANE_STATUS_INVAL - if the value doesn't fit inside the constraint
+ * or any other error occured
+ * @sa sanei_constrain_value()
+ */
+
+/** @fn extern SANE_Status sanei_constrain_value (const SANE_Option_Descriptor * opt, void * value, SANE_Word * info);
+ * Check the constraints of a SANE option and adjust its value if necessary.
+ *
+ * Depending on the type of the option and constraint, value is modified
+ * to fit inside constraint.
+ *
+ * @param opt option to check
+ * @param value value of the option
+ * @param info info is set to SANE_INFO_INEXACT if value was changed
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on success
+ * - SANE_STATUS_INVAL - if the function wasn't able to fit value into the
+ * constraint or any other error occured
+ * @sa sanei_check_value()
+ */
+
+/* @} */
+
+/* A few convenience macros: */
+/** @hideinitializer */
+#define NELEMS(a) ((int)(sizeof (a) / sizeof (a[0])))
+
+/** @hideinitializer */
+#define STRINGIFY1(x) #x
+/** @hideinitializer */
+#define STRINGIFY(x) STRINGIFY1(x)
+
+/** @hideinitializer */
+#define PASTE1(x,y) x##y
+/** @hideinitializer */
+#define PASTE(x,y) PASTE1(x,y)
+
+extern SANE_Status sanei_check_value (const SANE_Option_Descriptor * opt,
+ void * value);
+
+extern SANE_Status sanei_constrain_value (const SANE_Option_Descriptor * opt,
+ void * value, SANE_Word * info);
+
+
+#endif /* sanei_h */
diff --git a/include/sane/sanei_config.h b/include/sane/sanei_config.h
new file mode 100644
index 0000000..cf877cf
--- /dev/null
+++ b/include/sane/sanei_config.h
@@ -0,0 +1,175 @@
+/* sane - Scanner Access Now Easy.
+ Copyright (C) 1997 Jeffrey S. Freedman
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+*/
+
+/** @file sanei_config.h
+ * Generic configuration support.
+ *
+ * Use the functions of this header file if you want to read and analyze
+ * configuration files.
+*/
+
+#ifndef sanei_config_h
+#define sanei_config_h 1
+
+#include <stdio.h>
+#include <sane/sane.h>
+
+/** Search configuration file \a name along directory list and return file
+ * pointer if such a file exists.
+ *
+ * The following directory list is used:
+ * 1st: SANE_CONFIG_DIR environment variable.
+ * 2nd: PATH_SANE_CONFIG_DIR set during configuration.
+ * 3rd: Current directory.
+ * @param name filename with extension but without path (such as "mustek.conf")
+ *
+ * @return file pointer, or NULL if not found
+ *
+ */
+extern FILE *sanei_config_open (const char *name);
+
+/** Read a line from configuration file.
+ *
+ * Strips all unwanted chars. Use this instead of fgets() to remove
+ * line ending chars on all known platforms.
+ *
+ * @param str points to the buffer for the line
+ * @param n size of the buffer
+ * @param stream file pointer
+ *
+ * @return \a str on success and NULL on error
+*/
+extern char *sanei_config_read (char *str, int n, FILE *stream);
+
+/** Remove all whitespace from the beginning of a string.
+ *
+ * @param str string
+ *
+ * @return string without leading whitespace
+ *
+ */
+extern const char *sanei_config_skip_whitespace (const char *str);
+
+
+/** Scan a string constant from a line of text and return a malloced copy
+ * of it.
+ *
+ * It's the responsibility of the caller to free the returned string constant
+ * at an appropriate time. Whitespace in front of the string constant is
+ * ignored. Whitespace can be included in the string constant by enclosing it
+ * in double-quotes.
+ *
+ * @param str line of text to scan for a string constant
+ * @param string_const copy of the string constant
+ *
+ * @return a pointer to the position in str where the scan stopped
+ */
+extern const char *sanei_config_get_string (const char *str,
+ char **string_const);
+
+/** Expand device name patterns into a list of devices.
+ *
+ * Apart from a normal device name (such as /dev/sdb), this function currently
+ * supports SCSI device specifications of the form:
+ *
+ * scsi VENDOR MODEL TYPE BUS CHANNEL ID LUN
+ *
+ * Where VENDOR is the desired vendor name. MODEL is the desired model name.
+ * TYPE is the desired device type. All of these can be set to * to match
+ * anything. To include whitespace in these strings, enclose them in
+ * double-quotes ("). BUS, ID, and LUN are the desired SCSI bus, id, and
+ * logical-unit numbers. These can be set to * or simply omitted to match
+ * anything.
+ *
+ * @param name device name pattern
+ * @param attach attach function
+ */
+extern void sanei_config_attach_matching_devices (const char *name,
+ SANE_Status (*attach)
+ (const char *dev));
+
+/** this structure holds the description of configuration options. There is
+ * a list for options and another for their values.
+ * These lists are used when the configuration file is
+ * parsed. Read values are stored in Option_Value. Helpers functions are
+ * provided to access values easily */
+typedef struct
+{
+ /** number of options */
+ SANE_Int count;
+
+ /** NULL terminated list of configuration option */
+ SANE_Option_Descriptor **descriptors;
+
+ /** values for the configuration options */
+ void **values;
+
+} SANEI_Config;
+
+/** Parse configuration file, reading configuration options and trying to
+ * attach devices found in file.
+ *
+ * The option are gathered in a single configuration structure. Each time
+ * a line holds a value that is not an option, the attach function is called
+ * with the name found and the configuration structure with it's current values.
+ *
+ * @param config_file name of the configuration file to read
+ * @param config configuration structure to be filled during configuration
+ * parsing and passed to the attach callback function
+ * @param config_attach attach with config callback function
+ *
+ * @return SANE_STATUS_GOOD if no errors
+ * SANE_STATUS_ACCESS_DENIED if configuration file can't be opened
+ */
+extern SANE_Status sanei_configure_attach (
+ const char *config_file,
+ SANEI_Config *config,
+ SANE_Status (*config_attach)(SANEI_Config *config, const char *devname)
+);
+
+/** Return the list of config directories, extracted from the SANE_CONFIG_DIR
+ * environment variable and the default paths.
+ * @return a string containing the configuration paths, separated by the
+ * operating system's path separator
+ */
+extern const char *sanei_config_get_paths (void);
+
+#endif /* sanei_config_h */
diff --git a/include/sane/sanei_debug.h b/include/sane/sanei_debug.h
new file mode 100644
index 0000000..557f8a7
--- /dev/null
+++ b/include/sane/sanei_debug.h
@@ -0,0 +1,153 @@
+/** @file sanei_debug.h
+ * Support for printing debug messages.
+ *
+ * Use the functions of this header file to print debug or warning messages.
+ */
+
+#ifndef _SANEI_DEBUG_H
+#define _SANEI_DEBUG_H
+
+#include <sane/sanei.h>
+
+/** @name Public macros
+ * These macros can be used in backends and other SANE-related
+ * code.
+ *
+ * Before including sanei_debug.h, the following macros must be set:
+ *
+ * - BACKEND_NAME - The name of your backend without double-quotes (must be set in any case)
+ * - STUBS - If this is defined, no macros will be included. Used in
+ * backends consisting of more than one .c file.
+ * - DEBUG_DECLARE_ONLY - Generates prototypes instead of functions. Used in
+ * backends consisting of more than one .c file.
+ * - DEBUG_NOT_STATIC - Doesn't generate static functions. Used in header files if
+ * they are include in more than one .c file.
+ *
+ * @{
+ */
+
+/** @def DBG_INIT()
+ * Initialize sanei_debug.
+ *
+ * Call this function before you use any DBG function.
+ */
+
+/** @def DBG(level, fmt, ...)
+ * Print a message at debug level `level' or higher using a printf-like
+ * function. Example: DBG(1, "sane_open: opening fd \%d\\n", fd).
+ *
+ * @param level debug level
+ * @param fmt format (see man 3 printf for details)
+ * @param ... additional arguments
+ */
+
+/** @def IF_DBG(x)
+ * Compile code only if debugging is enabled.
+ *
+ * Expands to x if debug support is enabled at compile-time. If NDEBUG is
+ * defined at compile-time this macro expands to nothing.
+ *
+ * @param x code to expand when debugging is enabled
+ */
+
+/**
+ * @def DBG_LEVEL
+ * Current debug level.
+ *
+ * You can only read this "variable".
+ */
+
+/** @def ENTRY(name)
+ * Expands to sane_BACKEND_NAME_name.
+ *
+ * Example: ENTRY(init) in mustek.c will expand to sane_mustek_init.
+ */
+
+/* @} */
+
+
+ /** @hideinitializer*/
+#define ENTRY(name) PASTE(PASTE(PASTE(sane_,BACKEND_NAME),_),name)
+
+#ifdef NDEBUG
+
+extern void sanei_debug_ndebug (int level, const char *msg, ...);
+
+# define DBG_LEVEL (0)
+# define DBG_INIT()
+# define DBG sanei_debug_ndebug
+# define IF_DBG(x)
+
+#else /* !NDEBUG */
+
+ /** @hideinitializer*/
+# define DBG_LEVEL PASTE(sanei_debug_,BACKEND_NAME)
+
+# if defined(BACKEND_NAME) && !defined(STUBS)
+# ifdef DEBUG_DECLARE_ONLY
+extern int DBG_LEVEL;
+# else /* !DEBUG_DECLARE_ONLY */
+int DBG_LEVEL = 0;
+# endif /* DEBUG_DECLARE_ONLY */
+# endif /* BACKEND_NAME && !STUBS */
+
+ /** @hideinitializer*/
+# define DBG_INIT() \
+ sanei_init_debug (STRINGIFY(BACKEND_NAME), &DBG_LEVEL)
+
+ /** @hideinitializer*/
+# define DBG_LOCAL PASTE(DBG_LEVEL,_call)
+
+
+# ifndef STUBS
+
+# ifdef DEBUG_DECLARE_ONLY
+
+extern void DBG_LOCAL (int level, const char *msg, ...)
+#ifdef __GNUC__
+__attribute__ ((format (printf, 2, 3)))
+#endif
+;
+
+# else /* !DEBUG_DECLARE_ONLY */
+
+# include <stdarg.h>
+
+extern void sanei_debug_msg
+ (int level, int max_level, const char *be, const char *fmt, va_list ap);
+
+#ifdef __GNUC__
+# ifndef DEBUG_NOT_STATIC
+static
+# endif /* !DEBUG_NOT_STATIC */
+void DBG_LOCAL (int level, const char *msg, ...) __attribute__ ((format (printf, 2, 3)));
+#endif /* __GNUC__ */
+
+# ifndef DEBUG_NOT_STATIC
+static
+# endif /* !DEBUG_NOT_STATIC */
+void
+DBG_LOCAL (int level, const char *msg, ...)
+{
+ va_list ap;
+
+ va_start (ap, msg);
+ sanei_debug_msg (level, DBG_LEVEL, STRINGIFY(BACKEND_NAME), msg, ap);
+ va_end (ap);
+}
+
+# endif /* DEBUG_DECLARE_ONLY */
+
+# endif /* !STUBS */
+
+ /** @hideinitializer*/
+# define DBG DBG_LOCAL
+
+extern void sanei_init_debug (const char * backend, int * debug_level_var);
+
+ /** @hideinitializer*/
+# define IF_DBG(x) x
+
+#endif /* NDEBUG */
+
+#endif /* _SANEI_DEBUG_H */
diff --git a/include/sane/sanei_magic.h b/include/sane/sanei_magic.h
new file mode 100644
index 0000000..9e87e00
--- /dev/null
+++ b/include/sane/sanei_magic.h
@@ -0,0 +1,154 @@
+/* sane - Scanner Access Now Easy.
+
+ Copyright (C) 2009 m. allan noah
+
+ This file is part of the SANE package.
+
+ SANE is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ SANE is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+ License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with sane; see the file COPYING. If not, write to the Free
+ Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+*/
+
+/** @file sanei_magic.h
+ * This file provides an interface to simple image post-processing functions
+ *
+ * Currently, three operations are provided:
+ * - Deskew (correct rotated scans, by detecting media edges)
+ * - Autocrop (reduce image size to minimum rectangle containing media)
+ * - Despeckle (replace dots of significantly different color with background)
+ *
+ * Note that these functions are simplistic, and are expected to change.
+ * Patches and suggestions are welcome.
+ */
+
+#ifndef SANEI_MAGIC_H
+#define SANEI_MAGIC_H
+
+/** Initialize sanei_magic.
+ *
+ * Call this before any other sanei_magic function.
+ */
+extern void sanei_magic_init( void );
+
+/** Update the image buffer, replacing dots with surrounding background color
+ *
+ * @param params describes image
+ * @param buffer contains image data
+ * @param diam maximum dot diameter to remove
+ *
+ * @return
+ * - SANE_STATUS_GOOD - success
+ * - SANE_STATUS_INVAL - invalid image parameters
+ */
+extern SANE_Status
+sanei_magic_despeck (SANE_Parameters * params, SANE_Byte * buffer,
+ SANE_Int diam);
+
+/** Find the skew of the media inside the image, via edge detection.
+ *
+ * @param params describes image
+ * @param buffer contains image data
+ * @param dpiX horizontal resolution
+ * @param dpiY vertical resolution
+ * @param[out] centerX horizontal coordinate of center of rotation
+ * @param[out] centerY vertical coordinate of center of rotation
+ * @param[out] finSlope slope of rotation
+ *
+ * @return
+ * - SANE_STATUS_GOOD - success
+ * - SANE_STATUS_NO_MEM - not enough memory
+ * - SANE_STATUS_INVAL - invalid image parameters
+ * - SANE_STATUS_UNSUPPORTED - slope angle too shallow to detect
+ */
+extern SANE_Status
+sanei_magic_findSkew(const SANE_Parameters * params, const SANE_Byte * buffer,
+ int dpiX, int dpiY, int * centerX, int * centerY, double * finSlope);
+
+/** Correct the skew of the media inside the image, via simple rotation
+ *
+ * @param params describes image
+ * @param buffer contains image data
+ * @param centerX horizontal coordinate of center of rotation
+ * @param centerY vertical coordinate of center of rotation
+ * @param slope slope of rotation
+ * @param bg_color the replacement color for edges exposed by rotation
+ *
+ * @return
+ * - SANE_STATUS_GOOD - success
+ * - SANE_STATUS_NO_MEM - not enough memory
+ * - SANE_STATUS_INVAL - invalid image parameters
+ */
+extern SANE_Status
+sanei_magic_rotate (SANE_Parameters * params, SANE_Byte * buffer,
+ int centerX, int centerY, double slope, int bg_color);
+
+/** Find the edges of the media inside the image, parallel to image edges
+ *
+ * @param params describes image
+ * @param buffer contains image data
+ * @param dpiX horizontal resolution
+ * @param dpiY vertical resolution
+ * @param[out] top vertical offset to upper edge of media
+ * @param[out] bot vertical offset to lower edge of media
+ * @param[out] left horizontal offset to left edge of media
+ * @param[out] right horizontal offset to right edge of media
+ *
+ * @return
+ * - SANE_STATUS_GOOD - success
+ * - SANE_STATUS_NO_MEM - not enough memory
+ * - SANE_STATUS_UNSUPPORTED - edges could not be detected
+ */
+extern SANE_Status
+sanei_magic_findEdges(const SANE_Parameters * params, const SANE_Byte * buffer,
+ int dpiX, int dpiY, int * top, int * bot, int * left, int * right);
+
+/** Crop the image, parallel to image edges
+ *
+ * @param params describes image
+ * @param buffer contains image data
+ * @param top vertical offset to upper edge of crop
+ * @param bot vertical offset to lower edge of crop
+ * @param left horizontal offset to left edge of crop
+ * @param right horizontal offset to right edge of crop
+ *
+ * @return
+ * - SANE_STATUS_GOOD - success
+ * - SANE_STATUS_NO_MEM - not enough memory
+ * - SANE_STATUS_INVAL - invalid image parameters
+ */
+extern SANE_Status
+sanei_magic_crop(SANE_Parameters * params, SANE_Byte * buffer,
+ int top, int bot, int left, int right);
+
+#endif /* SANEI_MAGIC_H */
diff --git a/include/sane/sanei_pio.h b/include/sane/sanei_pio.h
new file mode 100644
index 0000000..61ac414
--- /dev/null
+++ b/include/sane/sanei_pio.h
@@ -0,0 +1,55 @@
+/* sane - Scanner Access Now Easy.
+ Copyright (C) 1998 Christian Bucher
+ Copyright (C) 1998 Kling & Hautzinger GmbH
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice. */
+
+#ifndef sanei_pio_h
+#define sanei_pio_h
+
+#include <sys/types.h>
+
+#include <sane/sane.h>
+
+extern SANE_Status sanei_pio_open (const char * dev, int * fd);
+extern void sanei_pio_close (int fd);
+extern int sanei_pio_read (int fd, u_char * buf, int n);
+extern int sanei_pio_write (int fd, const u_char * buf, int n);
+
+#endif /* sanei_pio_h */
+
diff --git a/include/sane/sanei_scsi.h b/include/sane/sanei_scsi.h
new file mode 100644
index 0000000..21c4561
--- /dev/null
+++ b/include/sane/sanei_scsi.h
@@ -0,0 +1,332 @@
+/* sane - Scanner Access Now Easy.
+ Copyright (C) 1996, 1997 David Mosberger-Tang
+ This file is part of the SANE package.
+
+ SANE is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ SANE is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+ License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with sane; see the file COPYING. If not, write to the Free
+ Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+*/
+
+/** @file sanei_scsi.h
+ * Generic interface to SCSI drivers.
+ * @sa sanei_usb.h, sanei_ab306.h,sanei_lm983x.h, sanei_pa4s2.h, sanei_pio.h,
+ * and man sane-scsi(5) for user-oriented documentation
+ */
+
+#ifndef sanei_scsi_h
+#define sanei_scsi_h
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+
+#include <sane/sane.h>
+
+/** Sense handler
+ *
+ * The sense handler can be implemented in backends. It's for deciding
+ * which sense codes should be considered an error and which shouldn't.
+ *
+ * @param fd file descriptor
+ * @param sense_buffer pointer to buffer containing sense codes
+ * @param arg pointer to data buffer
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on success (sense isn't regarded as error)
+ * - any other status if sense code is regarded as error
+ */
+typedef SANE_Status (*SANEI_SCSI_Sense_Handler) (int fd,
+ u_char *sense_buffer,
+ void *arg);
+/** Maximum size of a SCSI request
+ */
+extern int sanei_scsi_max_request_size;
+
+/** Find SCSI devices.
+ *
+ * Find each SCSI device that matches the pattern specified by the
+ * arguments. String arguments can be "omitted" by passing NULL,
+ * integer arguments can be "omitted" by passing -1.
+ *
+ * Example: vendor="HP" model=NULL, type=NULL, bus=3, id=-1, lun=-1 would
+ * attach all HP devices on SCSI bus 3.
+ *
+ * @param vendor
+ * @param model
+ * @param type
+ * @param bus
+ * @param channel
+ * @param id
+ * @param lun
+ * @param attach callback invoked once for each device, dev is the real devicename (passed to attach callback)
+ *
+ */
+extern void sanei_scsi_find_devices (const char *vendor, const char *model,
+ const char *type,
+ int bus, int channel, int id, int lun,
+ SANE_Status (*attach) (const char *dev));
+
+
+/** Open a SCSI device
+ *
+ * Opens a SCSI device by its device filename and returns a file descriptor.
+ * If it's necessary to adjust the SCSI buffer size, use
+ * sanei_scsi_open_extended().
+ *
+ * @param device_name name of the devicefile, e.g. "/dev/sg0"
+ * @param fd file descriptor
+ * @param sense_handler called whenever the SCSI driver returns a sense buffer
+ * @param sense_arg pointer to data for the sense handler
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on success
+ * - SANE_STATUS_ACCESS_DENIED - if the file couldn't be accessed due to
+ * permissions
+ * - SANE_STATUS_NO_MEM - if malloc failed (not enough memory)
+ * - SANE_STATUS_INVAL - if the filename was invalid or an unknown error occured
+ *
+ * @sa sanei_scsi_open_extended(), HAVE_SANEI_SCSI_OPEN_EXTENDED
+ */
+extern SANE_Status sanei_scsi_open (const char * device_name, int * fd,
+ SANEI_SCSI_Sense_Handler sense_handler,
+ void *sense_arg);
+
+/** Open a SCSI device and set the buffer size
+ *
+ * The extended open call allows a backend to ask for a specific buffer
+ * size. sanei_scsi_open_extended() tries to allocate a buffer of the size
+ * given by *buffersize upon entry to this function. If
+ * sanei_scsi_open_extended returns successfully, *buffersize contains the
+ * available buffer size. This value may be both smaller or larger than the
+ * value requested by the backend; it can even be zero. The backend must
+ * decide, if it got enough buffer memory to work.
+ *
+ * Note that the value of *buffersize may differ for different files.
+ *
+ * @param device_name name of the devicefile, e.g. "/dev/sg0"
+ * @param fd file descriptor
+ * @param sense_handler called whenever the SCSI driver returns a sense buffer
+ * @param sense_arg pointer to data for the sense handler
+ * @param buffersize size of the SCAI request buffer (in bytes)
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on success
+ * - SANE_STATUS_ACCESS_DENIED - if the file couldn't be accessed due to
+ * permissions
+ * - SANE_STATUS_NO_MEM - if malloc failed (not enough memory)
+ * - SANE_STATUS_INVAL - if the filename was invalid or an unknown error occured
+ *
+ * @sa sanei_scsi_open(), HAVE_SANEI_SCSI_OPEN_EXTENDED
+ */
+extern SANE_Status sanei_scsi_open_extended (
+ const char * device_name, int * fd,
+ SANEI_SCSI_Sense_Handler sense_handler,
+ void *sense_arg, int *buffersize);
+
+/** Do we have sanei_scsi_open_extended()?
+ *
+ * Let backends decide, which open call to use: if
+ * HAVE_SANEI_SCSI_OPEN_EXTENDED is defined, sanei_scsi_open_extended may be
+ * used. May also be used to decide, if sanei_scsi_req_flush_all or
+ * sanei_scsi_req_flush_all_extended() should be used.
+ *
+ * @sa sanei_scsi_open(), sanei_scsi_open_extended()
+*/
+#define HAVE_SANEI_SCSI_OPEN_EXTENDED
+
+/** Enqueue SCSI command
+ *
+ * One or more scsi commands can be enqueued by calling sanei_scsi_req_enter().
+ *
+ * NOTE: Some systems may not support multiple outstanding commands. On such
+ * systems, sanei_scsi_req_enter() may block. In other words, it is not proper
+ * to assume that enter() is a non-blocking routine.
+ *
+ * @param fd file descriptor
+ * @param src pointer to the SCSI command and associated write data (if any)
+ * @param src_size length of the command and data
+ * @param dst pointer to a buffer in which data is returned; NULL if no data is
+ * returned
+ * @param dst_size on input, the size of the buffer pointed to by dst, on exit,
+ * set to the number of bytes returned in the buffer (which is less than or equal
+ * to the buffer size; may be NULL if no data is expected
+ * @param idp pointer to a void* that uniquely identifies the entered request
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on success
+ * - SANE_STATUS_IO_ERROR - if an error was received from the SCSI driver
+ * - SANE_STATUS_NO_MEM - if malloc failed (not enough memory)
+ * - SANE_STATUS_INVAL - if a locking or an unknown error occured
+ * @sa sanei_scsi_req_enter2()
+ *
+*/
+extern SANE_Status sanei_scsi_req_enter (int fd,
+ const void * src, size_t src_size,
+ void * dst, size_t * dst_size,
+ void **idp);
+
+/** Enqueue SCSI command and separated data
+ *
+ * Same as sanei_scsi_req_enter(), but with separate buffers for the SCSI
+ * command and for the data to be sent to the device.
+ *
+ * With sanei_scsi_req_enter(), the length of te SCSI command block must be
+ * guessed. While that works in most cases, Canon scanners for example use the
+ * vendor specific commands 0xd4, 0xd5 and 0xd6. The Canon scanners want to
+ * get 6 byte command blocks for these commands, but sanei_scsi_req_enter() and
+ * sanei_scsi_cmd() send 12 bytes.
+ *
+ * If dst_size and *dst_size are non-zero, a "read command" (ie, data transfer
+ * from the device to the host) is assumed.
+ *
+ * @param fd file descriptor
+ * @param cmd pointer to SCSI command
+ * @param cmd_size size of the command
+ * @param src pointer to the buffer with data to be sent to the scanner
+ * @param src_size size of src buffer
+ * @param dst pointer to a buffer in which data is returned; NULL if no data is
+ * returned
+ * @param dst_size on input, the size of the buffer pointed to by dst, on exit,
+ * set to the number of bytes returned in the buffer (which is less than or equal
+ * to the buffer size; may be NULL if no data is expected
+ * @param idp pointer to a void* that uniquely identifies the entered request
+ * @return
+ * - SANE_STATUS_GOOD - on success
+ * - SANE_STATUS_IO_ERROR - if an error was received from the SCSI driver
+ * - SANE_STATUS_NO_MEM - if malloc failed (not enough memory)
+ * - SANE_STATUS_INVAL - if a locking or an unknown error occured
+ * @sa sanei_scsi_req_enter()
+ */
+extern SANE_Status sanei_scsi_req_enter2 (int fd,
+ const void * cmd, size_t cmd_size,
+ const void * src, size_t src_size,
+ void * dst, size_t * dst_size,
+ void **idp);
+
+/** Wait for SCSI command
+ *
+ * Wait for the completion of the SCSI command with id ID.
+ *
+ * @param id id used in sanei_scsi_req_enter()
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on success
+ * - SANE_STATUS_DEVICE_BUSY - if the device is busy (try again later)
+ * - SANE_STATUS_IO_ERROR - if an error was received from the SCSI driver
+*/
+extern SANE_Status sanei_scsi_req_wait (void *id);
+
+/** Send SCSI command
+ *
+ * This is a convenience function that is equivalent to a pair of
+ * sanei_scsi_req_enter()/sanei_scsi_req_wait() calls.
+ *
+ * @param fd file descriptor
+ * @param src pointer to the SCSI command and associated write data (if any)
+ * @param src_size length of the command and data
+ * @param dst pointer to a buffer in which data is returned; NULL if no data is
+ * returned
+ * @param dst_size on input, the size of the buffer pointed to by dst, on exit,
+ * set to the number of bytes returned in the buffer (which is less than or equal
+ * to the buffer size; may be NULL if no data is expected
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on success
+ * - SANE_STATUS_IO_ERROR - if an error was received from the SCSI driver
+ * - SANE_STATUS_NO_MEM - if malloc failed (not enough memory)
+ * - SANE_STATUS_INVAL - if a locking or an unknown error occured
+ *
+ * @sa sanei_scsi_cmd2(), sanei_scsi_req_enter(), sanei_scsi_req_wait()
+ */
+extern SANE_Status sanei_scsi_cmd (int fd,
+ const void * src, size_t src_size,
+ void * dst, size_t * dst_size);
+
+/** Send SCSI command and separated data
+ *
+ * This is a convenience function that is equivalent to a pair of
+ * sanei_scsi_req_enter2()/sanei_scsi_req_wait() calls.
+ *
+ * @param fd file descriptor
+ * @param cmd pointer to SCSI command
+ * @param cmd_size size of the command
+ * @param src pointer to the buffer with data to be sent to the scanner
+ * @param src_size size of src buffer
+ * @param dst pointer to a buffer in which data is returned; NULL if no data is
+ * returned
+ * @param dst_size on input, the size of the buffer pointed to by dst, on exit,
+ * set to the number of bytes returned in the buffer (which is less than or equal
+ * to the buffer size; may be NULL if no data is expected
+ * @return
+ * - SANE_STATUS_GOOD - on success
+ * - SANE_STATUS_IO_ERROR - if an error was received from the SCSI driver
+ * - SANE_STATUS_NO_MEM - if malloc failed (not enough memory)
+ * - SANE_STATUS_INVAL - if a locking or an unknown error occured
+ *
+ * @sa sanei_scsi_cmd(), sanei_scsi_req_enter(), sanei_scsi_req_wait()
+ */
+extern SANE_Status sanei_scsi_cmd2 (int fd,
+ const void * cmd, size_t cmd_size,
+ const void * src, size_t src_size,
+ void * dst, size_t * dst_size);
+
+/** Flush queue
+ *
+ * Flush all pending SCSI commands. This function work only, if zero or one
+ * SCSI file handles are open.
+ *
+ * @sa sanei_scsi_req_flush_all_extended()
+*/
+extern void sanei_scsi_req_flush_all (void);
+
+/** Flush queue for handle
+ *
+ * Flush all SCSI commands pending for one handle
+ *
+ * @param fd file descriptor
+ *
+ * @sa sanei_scsi_req_flush_all()
+ */
+extern void sanei_scsi_req_flush_all_extended (int fd);
+
+/** Close a SCSI device
+ *
+ * @param fd file descriptor
+ *
+ */
+extern void sanei_scsi_close (int fd);
+
+#endif /* sanei_scsi_h */
diff --git a/include/sane/sanei_usb.h b/include/sane/sanei_usb.h
new file mode 100644
index 0000000..c84e4b0
--- /dev/null
+++ b/include/sane/sanei_usb.h
@@ -0,0 +1,515 @@
+/* sane - Scanner Access Now Easy.
+ Copyright (C) 2001, 2002 Henning Meier-Geinitz
+ Copyright (C) 2003, 2005 Rene Rebe (sanei_read_int,sanei_set_timeout)
+ Copyright (C) 2008 m. allan noah (sanei_usb_clear_halt)
+ Copyright (C) 2011 Reinhold Kainhofer (sanei_usb_set_endpoint)
+ This file is part of the SANE package.
+
+ SANE is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ SANE is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+ License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with sane; see the file COPYING. If not, write to the Free
+ Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+*/
+
+/** @file sanei_usb.h
+ * This file provides a generic USB interface.
+ *
+ * Currently, two access methods to USB devices are provided:
+ * - Access to device
+ * files as used by the Linux kernel USB scanner driver is supported. FreeBSD
+ * and OpenBSD with their uscanner drivers also work this way. However,
+ * detection and control messages aren't supported on these platforms.
+ * - Access using libusb (where available).
+ *
+ * A general remark: Do not mix sanei_usb functions with "normal" file-related
+ * libc functions like open() or close. The device numbers used in sanei_usb
+ * are not file descriptors.
+ *
+ * @sa sanei_lm983x.h, sanei_pa4s2.h, sanei_pio.h, sanei_scsi.h, and <a
+ * href="http://www.sane-project.org/man/sane-usb.5.html">man sane-usb(5)</a>
+ * for user-oriented documentation
+ */
+
+#ifndef sanei_usb_h
+#define sanei_usb_h
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sane/sane.h>
+
+#include <stdlib.h> /* for size_t */
+
+/* USB spec defines */
+#ifndef USB_CLASS_PER_INTERFACE
+/* Also defined in libusb */
+/** @name Device and/or interface class codes */
+/* @{ */
+#define USB_CLASS_PER_INTERFACE 0x00
+#define USB_CLASS_AUDIO 0x01
+#define USB_CLASS_COMM 0x02
+#define USB_CLASS_HID 0x03
+#define USB_CLASS_PRINTER 0x07
+#define USB_CLASS_MASS_STORAGE 0x08
+#define USB_CLASS_HUB 0x09
+#define USB_CLASS_DATA 0x0a
+#define USB_CLASS_VENDOR_SPEC 0xff
+/* @} */
+
+/** @name USB descriptor types */
+/* @{ */
+#define USB_DT_DEVICE 0x01
+#define USB_DT_CONFIG 0x02
+#define USB_DT_STRING 0x03
+#define USB_DT_INTERFACE 0x04
+#define USB_DT_ENDPOINT 0x05
+#define USB_DT_HID 0x21
+#define USB_DT_REPORT 0x22
+#define USB_DT_PHYSICAL 0x23
+#define USB_DT_HUB 0x29
+/* @} */
+
+/** @name Descriptor sizes per descriptor type */
+/* @{ */
+#define USB_DT_DEVICE_SIZE 18
+#define USB_DT_CONFIG_SIZE 9
+#define USB_DT_INTERFACE_SIZE 9
+#define USB_DT_ENDPOINT_SIZE 7
+#define USB_DT_ENDPOINT_AUDIO_SIZE 9
+#define USB_DT_HUB_NONVAR_SIZE 7
+/* @} */
+
+/** @name Endpoint descriptors */
+/* @{ */
+#define USB_ENDPOINT_ADDRESS_MASK 0x0f
+#define USB_ENDPOINT_DIR_MASK 0x80
+#define USB_ENDPOINT_TYPE_MASK 0x03
+#define USB_ENDPOINT_TYPE_CONTROL 0
+#define USB_ENDPOINT_TYPE_ISOCHRONOUS 1
+#define USB_ENDPOINT_TYPE_BULK 2
+#define USB_ENDPOINT_TYPE_INTERRUPT 3
+/* @} */
+
+/** @name Standard requests */
+/* @{ */
+#define USB_REQ_GET_STATUS 0x00
+#define USB_REQ_CLEAR_FEATURE 0x01
+#define USB_REQ_SET_FEATURE 0x03
+#define USB_REQ_SET_ADDRESS 0x05
+#define USB_REQ_GET_DESCRIPTOR 0x06
+#define USB_REQ_SET_DESCRIPTOR 0x07
+#define USB_REQ_GET_CONFIGURATION 0x08
+#define USB_REQ_SET_CONFIGURATION 0x09
+#define USB_REQ_GET_INTERFACE 0x0A
+#define USB_REQ_SET_INTERFACE 0x0B
+#define USB_REQ_SYNCH_FRAME 0x0C
+/* @} */
+
+/** @name USB types */
+/* @{ */
+#define USB_TYPE_STANDARD (0x00 << 5)
+#define USB_TYPE_CLASS (0x01 << 5)
+#define USB_TYPE_VENDOR (0x02 << 5)
+#define USB_TYPE_RESERVED (0x03 << 5)
+/* @} */
+
+/** @name USB recipients */
+/* @{ */
+#define USB_RECIP_DEVICE 0x00
+#define USB_RECIP_INTERFACE 0x01
+#define USB_RECIP_ENDPOINT 0x02
+#define USB_RECIP_OTHER 0x03
+/* @} */
+
+#endif /* not USB_CLASS_PER_INTERFACE */
+
+/* Not defined in libsub */
+/** @name USB Masks */
+/* @{ */
+#define USB_TYPE_MASK (0x03 << 5)
+#define USB_RECIP_MASK 0x1f
+/* @} */
+
+/** @name USB directions */
+/* @{ */
+#define USB_DIR_OUT 0x00
+#define USB_DIR_IN 0x80
+/* @} */
+
+/** */
+struct sanei_usb_dev_descriptor
+{
+ SANE_Byte desc_type;
+ unsigned int bcd_usb;
+ unsigned int bcd_dev;
+ SANE_Byte dev_class;
+ SANE_Byte dev_sub_class;
+ SANE_Byte dev_protocol;
+ SANE_Byte max_packet_size;
+};
+
+/** Initialize sanei_usb.
+ *
+ * Call this before any other sanei_usb function.
+ */
+extern void sanei_usb_init (void);
+
+/** Get the vendor and product ids by device name.
+ *
+ * @param devname
+ * @param vendor vendor id
+ * @param product product id
+ *
+ * @return
+ * - SANE_STATUS_GOOD - if the ids could be determined
+ * - SANE_STATUS_INVAL - if the device is not found
+ * - SANE_STATUS_UNSUPPORTED - if this method is not supported with the current
+ * access method
+ */
+SANE_Status
+sanei_usb_get_vendor_product_byname (SANE_String_Const devname,
+ SANE_Word * vendor, SANE_Word * product);
+
+/** Get the vendor and product ids.
+ *
+ * Currently, only libusb devices and scanners supported by the Linux USB
+ * scanner module can be found. For the latter method, the Linux version
+ * must be 2.4.8 or higher.
+ *
+ * @param dn device number of an already sanei_usb_opened device
+ * @param vendor vendor id
+ * @param product product id
+ *
+ * @return
+ * - SANE_STATUS_GOOD - if the ids could be determined
+ * - SANE_STATUS_UNSUPPORTED - if the OS doesn't support detection of ids
+ */
+extern SANE_Status
+sanei_usb_get_vendor_product (SANE_Int dn, SANE_Word * vendor,
+ SANE_Word * product);
+
+/** Find devices that match given vendor and product ids.
+ *
+ * For limitations, see function sanei_usb_get_vendor_product().
+ * The function attach is called for every device which has been found.
+ *
+ * @param vendor vendor id
+ * @param product product id
+ * @param attach attach function
+ *
+ * @return SANE_STATUS_GOOD - on success (even if no scanner was found)
+ */
+extern SANE_Status
+sanei_usb_find_devices (SANE_Int vendor, SANE_Int product,
+ SANE_Status (*attach) (SANE_String_Const devname));
+
+/** Open a USB device.
+ *
+ * The device is opened by its name devname and the device number is
+ * returned in dn on success.
+ *
+ * Device names can be either device file names for direct access over
+ * kernel drivers (like /dev/usb/scanner) or libusb names. The libusb format
+ * looks like this: "libusb:bus-id:device-id". Bus-id and device-id are
+ * platform-dependent. An example could look like this: "libusb:001:002"
+ * (Linux).
+ *
+ * @param devname name of the device to open
+ * @param dn device number
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on success
+ * - SANE_STATUS_ACCESS_DENIED - if the file couldn't be accessed due to
+ * permissions
+ * - SANE_STATUS_INVAL - on every other error
+ */
+extern SANE_Status sanei_usb_open (SANE_String_Const devname, SANE_Int * dn);
+
+/** Set the endpoint for the USB communication
+ *
+ * Allows to switch to a different endpoint for the USB communication than
+ * the default (auto-detected) endpoint. This function can only be called
+ * after sanei_usb_open.
+ *
+ * @param dn device number
+ * @param ep_type type of endpoint to set (bitwise or of USB_DIR_IN/OUT and
+ * USB_ENDPOINT_TYPE_BULK/CONTROL/INTERRUPT/ISOCHRONOUS
+ * @param ep endpoint to use for the given type
+ *
+ */
+extern void sanei_usb_set_endpoint (SANE_Int dn, SANE_Int ep_type, SANE_Int ep);
+
+/** Retrieve the endpoint used for the USB communication
+ *
+ * Returns the endpoint used for the USB communication of the given type.
+ * This function can only be called after sanei_usb_open.
+ *
+ * @param dn device number
+ * @param ep_type type of endpoint to retrieve (bitwise or of USB_DIR_IN/OUT
+ * and USB_ENDPOINT_TYPE_BULK/CONTROL/INTERRUPT/ISOCHRONOUS
+ * @return endpoint used for the given type
+ *
+ */
+extern SANE_Int sanei_usb_get_endpoint (SANE_Int dn, SANE_Int ep_type);
+
+/** Close a USB device.
+ *
+ * @param dn device number
+ */
+extern void sanei_usb_close (SANE_Int dn);
+
+/** Set the libusb timeout for bulk and interrupt reads.
+ *
+ * @param timeout the new timeout in ms
+ */
+extern void sanei_usb_set_timeout (SANE_Int timeout);
+
+/** Check if sanei_usb_set_timeout() is available.
+ */
+#define HAVE_SANEI_USB_SET_TIMEOUT
+
+/** Clear halt condition on bulk endpoints
+ *
+ * @param dn device number
+ */
+extern SANE_Status sanei_usb_clear_halt (SANE_Int dn);
+
+/** Check if sanei_usb_clear_halt() is available.
+ */
+#define HAVE_SANEI_USB_CLEAR_HALT
+
+/** Reset device
+ *
+ * @param dn device number
+ */
+extern SANE_Status sanei_usb_reset (SANE_Int dn);
+
+/** Initiate a bulk transfer read.
+ *
+ * Read up to size bytes from the device to buffer. After the read, size
+ * contains the number of bytes actually read.
+ *
+ * @param dn device number
+ * @param buffer buffer to store read data in
+ * @param size size of the data
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on succes
+ * - SANE_STATUS_EOF - if zero bytes have been read
+ * - SANE_STATUS_IO_ERROR - if an error occured during the read
+ * - SANE_STATUS_INVAL - on every other error
+ *
+ */
+extern SANE_Status
+sanei_usb_read_bulk (SANE_Int dn, SANE_Byte * buffer, size_t * size);
+
+/** Initiate a bulk transfer write.
+ *
+ * Write up to size bytes from buffer to the device. After the write size
+ * contains the number of bytes actually written.
+ *
+ * @param dn device number
+ * @param buffer buffer to write to device
+ * @param size size of the data
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on succes
+ * - SANE_STATUS_IO_ERROR - if an error occured during the write
+ * - SANE_STATUS_INVAL - on every other error
+ */
+extern SANE_Status
+sanei_usb_write_bulk (SANE_Int dn, const SANE_Byte * buffer, size_t * size);
+
+/** Send/receive a control message to/from a USB device.
+ *
+ * This function is only supported for libusb devices and kernel acces with
+ * Linux 2.4.13 and newer.
+ * For a detailed explanation of the parameters, have a look at the USB
+ * specification at the <a href="http://www.usb.org/developers/docs/">
+ * www.usb.org developers information page</a>.
+ *
+ * @param dn device number
+ * @param rtype specifies the characteristics of the request (e.g. data
+ * direction)
+ * @param req actual request
+ * @param value parameter specific to the request
+ * @param index parameter specific to the request (often used to select
+ * endpoint)
+ * @param len length of data to send/receive
+ * @param data buffer to send/receive data
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on success
+ * - SANE_STATUS_IO_ERROR - on error
+ * - SANE_STATUS_UNSUPPORTED - if the feature is not supported by the OS or
+ * SANE.
+ */
+extern SANE_Status
+sanei_usb_control_msg (SANE_Int dn, SANE_Int rtype, SANE_Int req,
+ SANE_Int value, SANE_Int index, size_t * size,
+ SANE_Byte * data);
+
+/** Initiate a interrupt transfer read.
+ *
+ * Read up to size bytes from the interrupt endpoint from the device to
+ * buffer. After the read, size contains the number of bytes actually read.
+ *
+ * @param dn device number
+ * @param buffer buffer to store read data in
+ * @param size size of the data
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on succes
+ * - SANE_STATUS_EOF - if zero bytes have been read
+ * - SANE_STATUS_IO_ERROR - if an error occured during the read
+ * - SANE_STATUS_INVAL - on every other error
+ *
+ */
+
+extern SANE_Status
+sanei_usb_read_int (SANE_Int dn, SANE_Byte * buffer, size_t * size);
+
+/** Expand device name patterns into a list of devices.
+ *
+ * Apart from a normal device name (such as /dev/usb/scanner0 or
+ * libusb:002:003), this function currently supports USB device
+ * specifications of the form:
+ *
+ * usb VENDOR PRODUCT
+ *
+ * VENDOR and PRODUCT are non-negative integer numbers in decimal or
+ * hexadecimal format. A similar function for SCSI devices can be found
+ * in include/sane/config.h.
+ *
+ * @param name device name pattern
+ * @param attach attach function
+ *
+ */
+extern void
+sanei_usb_attach_matching_devices (const char *name,
+ SANE_Status (*attach) (const char *dev));
+
+/** Initiate set configuration.
+ *
+ * Change set configuration
+ *
+ * @param dn device number
+ * @param configuration, configuration nummber
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on succes
+ * - SANE_STATUS_EOF - if zero bytes have been read
+ * - SANE_STATUS_IO_ERROR - if an error occured during the read
+ * - SANE_STATUS_INVAL - on every other error
+ *
+ */
+
+extern SANE_Status
+sanei_usb_set_configuration (SANE_Int dn, SANE_Int configuration);
+
+/** Initiate claim interface.
+ *
+ * Change claim interface
+ *
+ * @param dn device number
+ * @param interface_number interface number
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on succes
+ * - SANE_STATUS_EOF - if zero bytes have been read
+ * - SANE_STATUS_IO_ERROR - if an error occured during the read
+ * - SANE_STATUS_INVAL - on every other error
+ *
+ */
+
+extern SANE_Status
+sanei_usb_claim_interface (SANE_Int dn, SANE_Int interface_number);
+
+/** Initiate release interface.
+ *
+ * Change release interface
+ *
+ * @param dn device number
+ * @param interface_number interface number
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on succes
+ * - SANE_STATUS_EOF - if zero bytes have been read
+ * - SANE_STATUS_IO_ERROR - if an error occured during the read
+ * - SANE_STATUS_INVAL - on every other error
+ *
+ */
+
+extern SANE_Status
+sanei_usb_release_interface (SANE_Int dn, SANE_Int interface_number);
+
+/** Initiate a set altinterface.
+ *
+ * Change set alternate
+ *
+ * @param dn device number
+ * @param alternate, alternate nummber
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on succes
+ * - SANE_STATUS_EOF - if zero bytes have been read
+ * - SANE_STATUS_IO_ERROR - if an error occured during the read
+ * - SANE_STATUS_INVAL - on every other error
+ *
+ */
+
+extern SANE_Status
+sanei_usb_set_altinterface (SANE_Int dn, SANE_Int alternate);
+
+/** Get some information from the device descriptor
+ *
+ * Sometimes it's useful to know something about revisions and
+ * other stuff reported by the USB system
+ *
+ * @param dn device number
+ * @param desc where to put the information to
+ *
+ * @return
+ * - SANE_STATUS_GOOD - on succes
+ * - SANE_STATUS_UNSUPPORTED - if the feature is not supported by the OS or
+ * SANE.
+ * - SANE_STATUS_INVAL - on every other error
+ *
+ */
+
+extern SANE_Status
+sanei_usb_get_descriptor( SANE_Int dn, struct sanei_usb_dev_descriptor *desc );
+
+/*------------------------------------------------------*/
+#endif /* sanei_usb_h */
diff --git a/lib/Makefile.am b/lib/Makefile.am
new file mode 100644
index 0000000..922ca44
--- /dev/null
+++ b/lib/Makefile.am
@@ -0,0 +1,61 @@
+## Makefile.am -- an automake template for a Makefile.in file
+## Copyright (C) 2004 Olaf Meeuwissen
+##
+## This file is part of the "Image Scan!" build infra-structure.
+##
+## The "Image Scan!" build infra-structure is free software.
+## You can redistribute it and/or modify it under the terms of the GNU
+## General Public License as published by the Free Software Foundation;
+## either version 2 of the License or at your option any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+## FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+## See the GNU General Public License for more details.
+##
+## You should have received a verbatim copy of the GNU General Public
+## License along with this program; if not, write to:
+##
+## Free Software Foundation, Inc.
+## 59 Temple Place, Suite 330
+## Boston, MA 02111-1307 USA
+
+
+SUBDIRS = \
+ pdf \
+ tests
+
+if ENABLE_FRONTEND
+noinst_LTLIBRARIES = libimage-stream.la
+libimage_stream_la_CPPFLAGS = -I$(top_srcdir)/include
+libimage_stream_la_LDFLAGS = -static
+libimage_stream_la_LIBADD = \
+ $(LIBLTDL) \
+ $(top_builddir)/lib/pdf/libpdf.la
+libimage_stream_la_SOURCES = \
+ $(libimage_stream_la_files)
+endif
+libimage_stream_la_files = \
+ basic-imgstream.cc \
+ basic-imgstream.hh \
+ fax-encoder.cc \
+ fax-encoder.hh \
+ file-opener.cc \
+ file-opener.hh \
+ imgstream.cc \
+ imgstream.hh \
+ jpegstream.cc \
+ jpegstream.hh \
+ pcxstream.cc \
+ pcxstream.hh \
+ pdfstream.cc \
+ pdfstream.hh \
+ pngstream.cc \
+ pngstream.hh \
+ pnmstream.cc \
+ pnmstream.hh \
+ tiffstream.cc \
+ tiffstream.hh
+
+EXTRA_DIST = \
+ $(libimage_stream_la_files)
diff --git a/lib/basic-imgstream.cc b/lib/basic-imgstream.cc
new file mode 100644
index 0000000..9b7aea8
--- /dev/null
+++ b/lib/basic-imgstream.cc
@@ -0,0 +1,301 @@
+// basic-imgstream.cc -- the mother of all image streams
+// Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "basic-imgstream.hh"
+
+#include <cstdlib>
+#include <argz.h>
+
+namespace iscan
+{
+
+#if __GLIBC_PREREQ(2, 10)
+ typedef const dirent** dirtype;
+#else
+ typedef const void* dirtype;
+#endif
+
+ basic_imgstream::basic_imgstream (void)
+ : _h_sz (0), _v_sz (0), _hres (0), _vres (0), _bits (0), _cspc (NONE)
+ {
+ }
+
+ basic_imgstream::~basic_imgstream (void)
+ {
+ }
+
+ basic_imgstream&
+ basic_imgstream::flush (void)
+ {
+ return *this;
+ }
+
+ basic_imgstream&
+ basic_imgstream::size (size_type h_sz, size_type v_sz)
+ {
+ _h_sz = h_sz;
+ _v_sz = v_sz;
+ return *this;
+ }
+
+ basic_imgstream&
+ basic_imgstream::resolution (size_type hres, size_type vres)
+ {
+ _hres = hres;
+ _vres = vres;
+ return *this;
+ }
+
+ basic_imgstream&
+ basic_imgstream::depth (size_type bits)
+ {
+ _bits = bits;
+ return *this;
+ }
+
+ basic_imgstream&
+ basic_imgstream::colour (colour_space space)
+ {
+ _cspc = space;
+ return *this;
+ }
+
+
+ basic_imgstream::dl_handle
+ basic_imgstream::dlopen (const char *libname,
+ bool (*validate) (lt_dlhandle))
+ {
+ if (0 != lt_dlinit ())
+ {
+ throw runtime_error (lt_dlerror ());
+ }
+
+ dl_handle lib = find_dlopen (libname, validate);
+ if (!lib)
+ {
+ lt_dlexit ();
+ throw runtime_error ("no usable library found");
+ }
+
+ return lib;
+ }
+
+ basic_imgstream::dl_ptr
+ basic_imgstream::dlsym (dl_handle lib, const char *funcname)
+ {
+ return lt_dlsym (lib, funcname);
+ }
+
+ int
+ basic_imgstream::dlclose (dl_handle lib)
+ {
+ return lt_dlclose (lib);
+ }
+
+ // forward declarations
+ static int reversionsort (dirtype, dirtype);
+ int selector (const dirent *);
+
+ //!
+ /*! A number of distributions seems to have switched to a policy where
+ the lib*.so files are provided by their -devel packages. Moreover,
+ the typical workstation install does not include such packages and
+ lt_dlopenext() will understandably have trouble finding your lib*.
+
+ This function is a fallback for such cases. It will look for your
+ library in the exact same places as lt_dlopenext(), but with this
+ difference that it will try to open any file that matches lib*.so,
+ starting with the one with the highest version number.
+
+ Actually, it is just as smart lt_dlopenext() and uses the correct
+ shared library extension for your platform. However, it does not
+ try libtool's .la extension.
+
+ The general policy for memory allocation and access problems is to
+ ignore them and muddle on or return the current result rightaway.
+
+ This function returns NULL if no suitable library could be found
+ and a handle to library otherwise.
+ */
+ basic_imgstream::dl_handle
+ basic_imgstream::find_dlopen (const char *libname,
+ bool (*validate) (lt_dlhandle))
+ {
+ using std::bad_alloc;
+ using std::string;
+
+ dl_handle result = NULL;
+
+ try
+ { // prepping the selector()
+ char *name = new char[strlen (libname)
+ + strlen (LT_MODULE_EXT) + 1];
+ name = strcpy (name, libname);
+ name = strcat (name, LT_MODULE_EXT);
+
+ _libname = name; // deleting _libname below, never mind
+ // that name goes out of scope here
+ }
+ catch (bad_alloc& oops)
+ {
+ return result;
+ }
+
+ char *pathz = NULL;
+ size_t length = 0;
+ bool is_pathz_ok = true;
+ { // set up a library search path like
+ // that used by lt_dlopen()
+ int delimiter = ':';
+
+ const char *path = NULL;
+
+ if ((path = lt_dlgetsearchpath ())
+ && 0 != argz_add_sep (&pathz, &length, path, delimiter))
+ {
+ is_pathz_ok = false;
+ }
+ if ((path = getenv ("LTDL_LIBRARY_PATH"))
+ && 0 != argz_add_sep (&pathz, &length, path, delimiter))
+ {
+ is_pathz_ok = false;
+ }
+ if ((path = getenv (LT_MODULE_PATH_VAR))
+ && 0 != argz_add_sep (&pathz, &length, path, delimiter))
+ {
+ is_pathz_ok = false;
+ }
+ if ("x86_64" == string (ISCAN_HOST_CPU)
+ && (path = "/usr/local/lib64:/usr/lib64:/lib64")
+ && 0 != argz_add_sep (&pathz, &length, path, delimiter))
+ {
+ is_pathz_ok = false;
+ }
+ // Kludge for multiarch support introduced in Ubuntu 11.04
+ if ("x86_64" == string (ISCAN_HOST_CPU))
+ {
+ if ((path = "/usr/lib/x86_64-linux-gnu:/lib/x86_64-linux-gnu")
+ && 0 != argz_add_sep (&pathz, &length, path, delimiter))
+ {
+ is_pathz_ok = false;
+ }
+ }
+ else
+ {
+ if ((path = "/usr/lib/i386-linux-gnu:/lib/i386-linux-gnu")
+ && 0 != argz_add_sep (&pathz, &length, path, delimiter))
+ {
+ is_pathz_ok = false;
+ }
+ }
+ if ((path = LT_DLSEARCH_PATH)
+ && 0 != argz_add_sep (&pathz, &length, path, delimiter))
+ {
+ is_pathz_ok = false;
+ }
+ }
+
+ if (is_pathz_ok)
+ { // go fetch!
+ const char *dir_name = NULL;
+ while (!result
+ && (dir_name = argz_next (pathz, length, dir_name)))
+ {
+ struct dirent **match = NULL;
+ int count = scandir (dir_name, &match, selector, reversionsort);
+
+ for (int i = 0; !result && i < count; ++i)
+ {
+
+ const char *file_name = match[i]->d_name;
+ try
+ {
+ char *abs_file_name
+ = new char[strlen (dir_name) + strlen ("/")
+ + strlen (file_name) + 1];
+ strcpy (abs_file_name, dir_name);
+ strcat (abs_file_name, "/");
+ strcat (abs_file_name, file_name);
+
+ result = lt_dlopen (abs_file_name);
+ if (validate && !validate (result))
+ {
+ lt_dlclose (result);
+ result = NULL;
+ }
+ delete [] abs_file_name;
+ }
+ catch (bad_alloc& oops)
+ {
+ // just ignore and continue with the next match
+ }
+ free (match[i]);
+ }
+ if (match) free (match); // malloc'd by scandir()
+ }
+ }
+
+ delete [] _libname; // we new'd a name for our selector()
+ free (pathz); // malloc'd by argz_add_sep()
+
+ return result;
+ }
+
+ //! Library name we are looking for.
+ /*! The scandir() API does not allow for passing arbitrary data to the
+ selector(). We use this variable to work around that limitation.
+
+ Note that this makes users of selector() thread unsafe.
+ */
+ const char *basic_imgstream::_libname = NULL;
+
+ //! Selects relevant library filenames.
+ /*! Returns true if the leading part of the directory entry's file
+ name matches the library name we are looking for. The file name
+ may contain trailing version information which is ignored.
+ */
+ int
+ selector (const dirent *dir)
+ {
+ return (0 == strncmp (dir->d_name, basic_imgstream::_libname,
+ strlen (basic_imgstream::_libname)));
+ }
+
+ //! The C library's versionsort() function in reverse.
+ static int
+ reversionsort (dirtype a, dirtype b)
+ {
+ return versionsort (b, a);
+ }
+
+} // namespace iscan
diff --git a/lib/basic-imgstream.hh b/lib/basic-imgstream.hh
new file mode 100644
index 0000000..f55bc60
--- /dev/null
+++ b/lib/basic-imgstream.hh
@@ -0,0 +1,144 @@
+// basic-imgstream.hh -- the mother of all image streams
+// Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifndef iscan_basic_imgstream_hh_included
+#define iscan_basic_imgstream_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+
+#ifndef LT_MODULE_EXT
+#define LT_MODULE_EXT LTDL_SHLIB_EXT
+#endif
+
+#ifndef LT_MODULE_PATH_VAR
+#define LT_MODULE_PATH_VAR LTDL_SHLIBPATH_VAR
+#endif
+
+#ifndef LT_DLSEARCH_PATH
+#define LT_DLSEARCH_PATH LTDL_SYSSEARCHPATH
+#endif
+
+#endif
+
+#include <dirent.h>
+#include <ltdl.h>
+#include <stdexcept>
+
+namespace iscan
+{
+ using std::runtime_error;
+
+ enum colour_space
+ {
+ NONE,
+
+ monochrome,
+ mono = monochrome,
+
+ grayscale,
+ greyscale = grayscale,
+ gray = grayscale,
+ grey = grayscale,
+
+ RGB,
+ RGB_alpha
+ };
+
+ class basic_imgstream
+ {
+ public:
+ typedef char byte_type;
+ typedef size_t size_type;
+
+ virtual ~basic_imgstream (void);
+
+ virtual basic_imgstream& write (const byte_type *line, size_type n) = 0;
+ virtual basic_imgstream& flush (void);
+
+ virtual basic_imgstream& size (size_type h_sz, size_type v_sz);
+ virtual basic_imgstream& resolution (size_type hres, size_type vres);
+ virtual basic_imgstream& depth (size_type bits);
+ virtual basic_imgstream& colour (colour_space space);
+ virtual void rotate_180 (bool yes) {};
+
+ protected:
+ basic_imgstream (void);
+
+ size_type _h_sz;
+ size_type _v_sz;
+ size_type _hres;
+ size_type _vres;
+
+ size_type _bits;
+ colour_space _cspc;
+
+ private: // undefined to prevent copying
+ basic_imgstream (const basic_imgstream&);
+ basic_imgstream& operator= (const basic_imgstream&);
+
+
+ // API to deal with external image format libraries
+
+ protected:
+ typedef lt_dlhandle dl_handle;
+ typedef lt_ptr dl_ptr;
+
+ static dl_handle dlopen (const char *libname,
+ bool (*validate) (dl_handle) = NULL);
+ static dl_ptr dlsym (dl_handle lib, const char *funcname);
+ static int dlclose (dl_handle lib);
+
+ private:
+ static dl_handle find_dlopen (const char *libname,
+ bool (*validate) (dl_handle));
+ static const char *_libname;
+
+ friend int selector (const dirent *);
+
+#ifdef __GNUC__
+#define fundecl(returntype,funcname,arglist...) \
+ typedef returntype (*funcname##_f) (arglist); \
+ funcname##_f funcname;
+#else
+#error "Your compiler is not known to support macros with a variable"
+#error "number of arguments. In case it does, please report this to"
+#error "the library maintainers and include a suitable preprocessor"
+#error "check for them to add. A patch will be most appreciated."
+#endif
+
+ };
+
+} // namespace iscan
+
+#endif /* !defined (iscan_basic_imgstream_hh_included) */
diff --git a/lib/fax-encoder.cc b/lib/fax-encoder.cc
new file mode 100644
index 0000000..90abe8c
--- /dev/null
+++ b/lib/fax-encoder.cc
@@ -0,0 +1,411 @@
+// fax-encoder.cc -- convert scanlines to fascimile format
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "fax-encoder.hh"
+
+#include <vector>
+#include <stdint.h>
+
+#define WHITE false
+#define BLACK true
+
+namespace iscan
+{
+ using namespace std;
+
+ struct code {
+ unsigned int bits;
+ unsigned int code;
+ };
+
+ static const size_t g3_make_up_min = 64;
+ static const size_t g3_make_up_inc = 64;
+ static const size_t g3_extra_make_up_min = 1792;
+ static const size_t g3_extra_make_up_max = 2560;
+
+ //! Terminating codes for white runs of less than 64 pixels.
+ static const struct code g3_white_terminal[] =
+ {
+ { 8, 0x35 },
+ { 6, 0x07 },
+ { 4, 0x07 },
+ { 4, 0x08 },
+ { 4, 0x0b },
+ { 4, 0x0c },
+ { 4, 0x0e },
+ { 4, 0x0f },
+ { 5, 0x13 },
+ { 5, 0x14 },
+ { 5, 0x07 },
+ { 5, 0x08 },
+ { 6, 0x08 },
+ { 6, 0x03 },
+ { 6, 0x34 },
+ { 6, 0x35 },
+ { 6, 0x2a },
+ { 6, 0x2b },
+ { 7, 0x27 },
+ { 7, 0x0c },
+ { 7, 0x08 },
+ { 7, 0x17 },
+ { 7, 0x03 },
+ { 7, 0x04 },
+ { 7, 0x28 },
+ { 7, 0x2b },
+ { 7, 0x13 },
+ { 7, 0x24 },
+ { 7, 0x18 },
+ { 8, 0x02 },
+ { 8, 0x03 },
+ { 8, 0x1a },
+ { 8, 0x1b },
+ { 8, 0x12 },
+ { 8, 0x13 },
+ { 8, 0x14 },
+ { 8, 0x15 },
+ { 8, 0x16 },
+ { 8, 0x17 },
+ { 8, 0x28 },
+ { 8, 0x29 },
+ { 8, 0x2a },
+ { 8, 0x2b },
+ { 8, 0x2c },
+ { 8, 0x2d },
+ { 8, 0x04 },
+ { 8, 0x05 },
+ { 8, 0x0a },
+ { 8, 0x0b },
+ { 8, 0x52 },
+ { 8, 0x53 },
+ { 8, 0x54 },
+ { 8, 0x55 },
+ { 8, 0x24 },
+ { 8, 0x25 },
+ { 8, 0x58 },
+ { 8, 0x59 },
+ { 8, 0x5a },
+ { 8, 0x5b },
+ { 8, 0x4a },
+ { 8, 0x4b },
+ { 8, 0x32 },
+ { 8, 0x33 },
+ { 8, 0x34 },
+ };
+
+ //! Terminating codes for black runs of less than 64 pixels.
+ static const struct code g3_black_terminal[] =
+ {
+ { 10, 0x37 },
+ { 3, 0x02 },
+ { 2, 0x03 },
+ { 2, 0x02 },
+ { 3, 0x03 },
+ { 4, 0x03 },
+ { 4, 0x02 },
+ { 5, 0x03 },
+ { 6, 0x05 },
+ { 6, 0x04 },
+ { 7, 0x04 },
+ { 7, 0x05 },
+ { 7, 0x07 },
+ { 8, 0x04 },
+ { 8, 0x07 },
+ { 9, 0x18 },
+ { 10, 0x17 },
+ { 10, 0x18 },
+ { 10, 0x08 },
+ { 11, 0x67 },
+ { 11, 0x68 },
+ { 11, 0x6c },
+ { 11, 0x37 },
+ { 11, 0x28 },
+ { 11, 0x17 },
+ { 11, 0x18 },
+ { 12, 0xca },
+ { 12, 0xcb },
+ { 12, 0xcc },
+ { 12, 0xcd },
+ { 12, 0x68 },
+ { 12, 0x69 },
+ { 12, 0x6a },
+ { 12, 0x6b },
+ { 12, 0xd2 },
+ { 12, 0xd3 },
+ { 12, 0xd4 },
+ { 12, 0xd5 },
+ { 12, 0xd6 },
+ { 12, 0xd7 },
+ { 12, 0x6c },
+ { 12, 0x6d },
+ { 12, 0xda },
+ { 12, 0xdb },
+ { 12, 0x54 },
+ { 12, 0x55 },
+ { 12, 0x56 },
+ { 12, 0x57 },
+ { 12, 0x64 },
+ { 12, 0x65 },
+ { 12, 0x52 },
+ { 12, 0x53 },
+ { 12, 0x24 },
+ { 12, 0x37 },
+ { 12, 0x38 },
+ { 12, 0x27 },
+ { 12, 0x28 },
+ { 12, 0x58 },
+ { 12, 0x59 },
+ { 12, 0x2b },
+ { 12, 0x2c },
+ { 12, 0x5a },
+ { 12, 0x66 },
+ { 12, 0x67 },
+ };
+
+ //! Make up codes for white runs of 64 to 1728 + 63 pixels.
+ static const struct code g3_white_make_up[] =
+ { // runlength 64 + index * 64
+ { 5, 0x1b },
+ { 5, 0x12 },
+ { 6, 0x17 },
+ { 7, 0x37 },
+ { 8, 0x36 },
+ { 8, 0x37 },
+ { 8, 0x64 },
+ { 8, 0x65 },
+ { 8, 0x68 },
+ { 8, 0x67 },
+ { 9, 0xcc },
+ { 9, 0xcd },
+ { 9, 0xd2 },
+ { 9, 0xd3 },
+ { 9, 0xd4 },
+ { 9, 0xd5 },
+ { 9, 0xd6 },
+ { 9, 0xd7 },
+ { 9, 0xd8 },
+ { 9, 0xd9 },
+ { 9, 0xda },
+ { 9, 0xdb },
+ { 9, 0x98 },
+ { 9, 0x99 },
+ { 9, 0x9a },
+ { 6, 0x18 },
+ { 9, 0x9b },
+ };
+
+ //! Make up codes for black runs of 64 to 1728 + 63 pixels.
+ static const struct code g3_black_make_up[] =
+ { // runlength 64 + index * 64
+ { 10, 0x0f },
+ { 12, 0xc8 },
+ { 12, 0xc9 },
+ { 12, 0x5b },
+ { 12, 0x33 },
+ { 12, 0x34 },
+ { 12, 0x35 },
+ { 13, 0x6c },
+ { 13, 0x6d },
+ { 13, 0x4a },
+ { 13, 0x4b },
+ { 13, 0x4c },
+ { 13, 0x4d },
+ { 13, 0x72 },
+ { 13, 0x73 },
+ { 13, 0x74 },
+ { 13, 0x75 },
+ { 13, 0x76 },
+ { 13, 0x77 },
+ { 13, 0x52 },
+ { 13, 0x53 },
+ { 13, 0x54 },
+ { 13, 0x55 },
+ { 13, 0x5a },
+ { 13, 0x5b },
+ { 13, 0x64 },
+ { 13, 0x65 },
+ };
+
+ //! Additional make up codes for run of more than 1792 pixels.
+ static const struct code g3_extra_make_up[] =
+ { // runlength 1792 + index * 64
+ { 11, 0x08 },
+ { 11, 0x0c },
+ { 11, 0x0d },
+ { 12, 0x12 },
+ { 12, 0x13 },
+ { 12, 0x14 },
+ { 12, 0x15 },
+ { 12, 0x16 },
+ { 12, 0x17 },
+ { 12, 0x1c },
+ { 12, 0x1d },
+ { 12, 0x1e },
+ { 12, 0x1f },
+ };
+
+ static string transform (vector<size_t>& runs);
+
+ //! Converts a packed \a line of pixels into FAX G3 encoded scanline.
+ /*! This functions merely collects the run lengths into a vector and
+ passes that vector off to transform().
+ */
+ string
+ fax_encoder::operator() (const byte_type* line, size_type n)
+ {
+ bool colour = WHITE;
+ size_t length = 0;
+ vector<size_t> runs;
+
+ uint8_t bit = 0x80;
+
+ n *= 8;
+
+ while (0 < n--)
+ {
+ if (colour == bool ((*line | ~bit) & bit))
+ {
+ ++length;
+ }
+ else
+ {
+ runs.push_back (length);
+ colour = (WHITE == colour ? BLACK : WHITE);
+ length = 1;
+ }
+
+ bit >>= 1;
+ if (0 == bit)
+ {
+ bit = 0x80;
+ ++line;
+ }
+ }
+ runs.push_back (length);
+
+ return transform (runs);
+ }
+
+ //! Converts a vector of run lengths into a FAX G3 encoded string.
+ /*! The string always starts with an end-of-line marker and will be
+ filled if necessary.
+ */
+ static string
+ transform (vector<size_t>& runs)
+ {
+ vector<size_t>::iterator it = runs.begin ();
+ bool colour = WHITE;
+
+ string result;
+ unsigned char ch = 0x00;
+ size_t i = 0;
+
+ unsigned int mask = 1 << 11;
+ unsigned int code = 1;
+
+ while (mask)
+ {
+ if (code & mask) ch |= (1 << (7 - i % 8));
+ ++i;
+ mask >>= 1;
+ if (0 == i % 8)
+ {
+ result.push_back (ch);
+ ch = 0x00;
+ }
+ }
+
+ while (runs.end () != it)
+ {
+ bool terminal = false;
+ do
+ {
+ const struct code *c = NULL;
+
+ if (g3_extra_make_up_max <= *it)
+ {
+ size_t index = ((g3_extra_make_up_max - g3_extra_make_up_min)
+ / g3_make_up_inc);
+
+ c = g3_extra_make_up + index;
+ *it -= g3_extra_make_up_max;
+ }
+ else if (g3_extra_make_up_min <= *it)
+ {
+ size_t index = (*it - g3_extra_make_up_min) / g3_make_up_inc;
+
+ c = g3_extra_make_up + index;
+ *it -= g3_extra_make_up_min + index * g3_make_up_inc;
+ }
+ else if (g3_make_up_min <= *it)
+ {
+ size_t index = (*it - g3_make_up_min) / g3_make_up_inc;
+
+ c = ((WHITE == colour ? g3_white_make_up : g3_black_make_up)
+ + index);
+ *it -= g3_make_up_min + index * g3_make_up_inc;
+ }
+ else
+ {
+ c = ((WHITE == colour ? g3_white_terminal : g3_black_terminal)
+ + *it);
+ *it = 0;
+ terminal = true;
+ }
+
+ unsigned int mask = 1 << (c->bits - 1);
+ unsigned int code = c->code;
+
+ while (mask)
+ {
+ if (code & mask) ch |= (1 << (7 - i % 8));
+ ++i;
+ mask >>= 1;
+ if (0 == i % 8)
+ {
+ result.push_back (ch);
+ ch = 0x00;
+ }
+ }
+ }
+ while (!terminal);
+
+ ++it;
+ colour = (WHITE == colour ? BLACK : WHITE);
+ }
+ if (0 != i % 8)
+ result.push_back (ch);
+
+ return result;
+ }
+
+} // namespace iscan
diff --git a/lib/fax-encoder.hh b/lib/fax-encoder.hh
new file mode 100644
index 0000000..4ae6f38
--- /dev/null
+++ b/lib/fax-encoder.hh
@@ -0,0 +1,58 @@
+// fax-encoder.hh -- convert scanlines to fascimile format
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifndef iscan_fax_encoder_hh_included
+#define iscan_fax_encoder_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "basic-imgstream.hh"
+
+namespace iscan
+{
+ using std::string;
+
+ class fax_encoder
+ {
+ public:
+ typedef basic_imgstream::byte_type byte_type;
+ typedef basic_imgstream::size_type size_type;
+
+ string operator() (const byte_type* line, size_type n);
+ };
+
+} // namespace iscan
+
+#endif /* !defined (iscan_fax_encoder_hh_included) */
diff --git a/lib/file-opener.cc b/lib/file-opener.cc
new file mode 100644
index 0000000..c0ccdd8
--- /dev/null
+++ b/lib/file-opener.cc
@@ -0,0 +1,321 @@
+// file-opener.cc -- dealing with files when doing multi-image scans
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "file-opener.hh"
+
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <list>
+#include <iomanip>
+#include <sstream>
+#include <stdexcept>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+namespace iscan
+{
+ static string tempfile (const string& dirname = string ());
+
+ //! Opening one or more files in a temporary file location.
+ file_opener::file_opener (bool collate)
+ : _collate (collate), _filename (string ()), _tempfile (string ()),
+ _fp (NULL), _pattern (NULL)
+ {
+ }
+
+ //! Opening a file by \a name.
+ file_opener::file_opener (const string& name)
+ : _collate (true), _filename (string ()), _tempfile (string ()),
+ _fp (NULL), _pattern (NULL)
+ {
+ common_init (name);
+ }
+
+ //! Opening files following a naming \a pattern.
+ file_opener::file_opener (const string& pattern, unsigned int start_index)
+ : _collate (false), _filename (string ()), _tempfile (string ()),
+ _fp (NULL), _pattern (NULL)
+ {
+ common_init (pattern);
+
+ string::size_type hash = _pattern->basename.find_last_not_of (hash_mark);
+
+ if (string::npos == hash)
+ {
+ delete _pattern; // new'd in common_init()
+ throw std::invalid_argument ("invalid file name pattern");
+ }
+ ++hash; // first hash_mark
+
+ _pattern->digits = _pattern->basename.length () - hash;
+ _pattern->basename = _pattern->basename.substr (0, hash);
+ _pattern->index = start_index;
+ }
+
+ file_opener::~file_opener (void)
+ {
+ if (_fp) close ();
+ if (_tempfile != _filename)
+ rename ();
+ delete _pattern;
+ }
+
+ //! Returns the C \c FILE pointer associated with a name().
+ file_opener::operator FILE * (void)
+ {
+ if (_filename.empty ()) set_names ();
+ if (!_fp) open ();
+ return _fp;
+ }
+
+ //! Returns the pathname of the output destination.
+ const string&
+ file_opener::name (void) const
+ {
+ if (_filename.empty ())
+ const_cast<file_opener *> (this)->set_names ();
+ return _filename;
+ }
+
+ //! Returns the pathname of the interim output destination.
+ /*! \deprecated Flaming hack to quickly fix our TIFF support.
+ */
+ const string&
+ file_opener::temp (void) const
+ {
+ if (_filename.empty ())
+ const_cast<file_opener *> (this)->set_names ();
+ return _tempfile;
+ }
+
+ //! Returns the file's extension (without extension separator).
+ string
+ file_opener::extension (void) const
+ {
+ if (!_pattern) return null_ext;
+ return _pattern->extension.substr (ext_sep.size ());
+ }
+
+ //! Opens the next file, unless the file_opener is_collating().
+ file_opener&
+ file_opener::operator++ (void)
+ {
+ if (_collate) return *this;
+
+ if (_fp) close ();
+ if (_tempfile != _filename)
+ rename ();
+ set_names (true);
+ open ();
+
+ return *this;
+ }
+
+ //! Tells whether or not output will be collated.
+ bool
+ file_opener::is_collating (void) const
+ {
+ return _collate;
+ }
+
+ //! Removes the output destination.
+ void
+ file_opener::remove (void)
+ {
+ if (_fp)
+ {
+ close ();
+ if (0 != ::remove (_tempfile.c_str ()))
+ throw std::ios_base::failure (strerror (errno));
+ }
+ _tempfile = string ();
+ _filename = string ();
+ }
+
+ const string file_opener::dir_sep = "/";
+ const string file_opener::ext_sep = ".";
+ const char file_opener::hash_mark = '#';
+
+ const string file_opener::null_ext = string ();
+
+ //! Handles common part of the constructors taking a string.
+ void
+ file_opener::common_init (const string& s)
+ {
+ string::size_type sep = s.rfind (dir_sep);
+ string::size_type dot = s.rfind (ext_sep);
+
+ if (string::npos != sep && dot < sep)
+ dot = string::npos;
+
+ _pattern = new struct pattern;
+ if (string::npos != sep)
+ {
+ ++sep;
+ _pattern->dirname = s.substr (0, sep);
+ _pattern->basename = s.substr (sep, dot - sep);
+ }
+ else
+ {
+ _pattern->dirname = string ();
+ _pattern->basename = s.substr (0, dot);
+ }
+
+ if (string::npos != dot)
+ {
+ _pattern->extension = s.substr (dot);
+ }
+ else
+ {
+ _pattern->extension = null_ext;
+ }
+ _pattern->digits = 0;
+ _pattern->index = 0;
+ }
+
+ void
+ file_opener::set_names (bool next)
+ {
+ using std::stringstream;
+ using std::setfill;
+ using std::setw;
+
+ if (!_pattern)
+ {
+ _tempfile = tempfile ();
+ _filename = _tempfile;
+ }
+ else
+ {
+ if (next) ++_pattern->index;
+
+ stringstream ss;
+ ss << _pattern->dirname
+ << _pattern->basename;
+ if (_pattern->digits)
+ {
+ ss << setfill ('0')
+ << setw (_pattern->digits)
+ << _pattern->index;
+ }
+ ss << _pattern->extension;
+
+ _filename = ss.str ();
+ _tempfile = (!_pattern->digits
+ ? _filename : tempfile (_pattern->dirname.empty ()
+ ? "." : _pattern->dirname));
+ }
+ }
+
+ //! Error handling wrapper around the C fopen() call.
+ void
+ file_opener::open (void)
+ {
+ _fp = fopen (_tempfile.c_str (), "wb");
+ if (!_fp)
+ throw std::ios_base::failure (strerror (errno));
+ }
+
+ //! Error handling wrapper around the C fclose() call.
+ void
+ file_opener::close (void)
+ {
+ if (!_fp) return;
+
+ int rv = fclose (_fp);
+ _fp = NULL;
+
+ if (0 != rv)
+ throw std::ios_base::failure (strerror (errno));
+ }
+
+ void
+ file_opener::rename (void)
+ {
+ if (0 != ::rename (_tempfile.c_str (), _filename.c_str ()))
+ throw std::ios_base::failure (strerror (errno));
+
+ _tempfile = _filename;
+ }
+
+
+ //! Creates a temporary file in a secure way.
+ static string
+ tempfile (const string& dirname)
+ {
+ using std::list;
+
+ list<string> dirs;
+ if (!dirname.empty ()) dirs.push_back (dirname);
+ if (getenv ("TMPDIR")) dirs.push_back (getenv ("TMPDIR"));
+#ifdef P_tmpdir
+ dirs.push_back (P_tmpdir); // C library default
+#endif
+ dirs.push_back ("/tmp"); // last resort
+
+ string filename;
+ list<string>::iterator it = dirs.begin ();
+
+ while (dirs.end () != it && filename.empty ())
+ {
+ string ts = (*it + file_opener::dir_sep
+ + (!dirname.empty () ? "." : "")
+ + PACKAGE_TARNAME "XXXXXX");
+ char *tc = new char [ts.length() + 1];
+
+ ts.copy (tc, ts.length ());
+ tc[ts.length ()] = '\0';
+
+ mode_t um = umask (0);
+ umask (dirname.empty () ? 0077 : um); // use safe permissions
+ if (0 <= mkstemp (tc))
+ filename = tc;
+ umask (um);
+
+ // mkstemp from glibc 2.0.7 and later always uses 0600, (try
+ // to) revert here
+ if (!dirname.empty ())
+ {
+ chmod (filename.c_str (), ( S_IRUSR | S_IRGRP | S_IROTH
+ | S_IWUSR | S_IWGRP | S_IWOTH) & ~um);
+ }
+
+ delete [] tc;
+ ++it;
+ }
+
+ return filename;
+ }
+
+} // namespace iscan
diff --git a/lib/file-opener.hh b/lib/file-opener.hh
new file mode 100644
index 0000000..70c475c
--- /dev/null
+++ b/lib/file-opener.hh
@@ -0,0 +1,101 @@
+// file-opener.hh -- dealing with files when doing multi-image scans
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifndef iscan_file_opener_hh_included
+#define iscan_file_opener_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <cstdio>
+#include <string>
+
+namespace iscan
+{
+ using std::string;
+
+ class file_opener
+ {
+ public:
+ explicit file_opener (bool collate);
+ explicit file_opener (const string& name);
+ file_opener (const string& pattern, unsigned int start_index);
+ ~file_opener (void);
+
+ operator FILE * (void);
+
+ const string& name (void) const;
+ const string& temp (void) const;
+ string extension (void) const;
+
+ file_opener& operator++ (void);
+
+ bool is_collating (void) const;
+
+ void remove (void);
+
+ static const string dir_sep;
+ static const string ext_sep;
+ static const char hash_mark;
+
+ static const string null_ext;
+
+ private:
+ void common_init (const string& s);
+ void set_names (bool next = false);
+
+ void open (void);
+ void close (void);
+ void rename (void);
+
+ bool _collate;
+
+ string _filename;
+ string _tempfile;
+ FILE *_fp;
+
+ struct pattern
+ {
+ string extension;
+ string basename;
+ string dirname; // includes final dir_sep
+ size_t digits;
+ unsigned int index;
+ };
+ struct pattern *_pattern;
+ };
+
+} // namespace iscan
+
+#endif /* !defined (iscan_file_opener_hh_included) */
diff --git a/lib/imgstream.cc b/lib/imgstream.cc
new file mode 100644
index 0000000..80f87fb
--- /dev/null
+++ b/lib/imgstream.cc
@@ -0,0 +1,142 @@
+// imgstream.cc -- provides an interface to write multiple images
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "imgstream.hh"
+
+#include "jpegstream.hh"
+#include "pcxstream.hh"
+#include "pngstream.hh"
+#include "pnmstream.hh"
+#include "pdfstream.hh"
+#include "tiffstream.hh"
+
+namespace iscan
+{
+ imgstream::imgstream (file_opener& opener, file_format format,
+ bool match_direction)
+ : _page (0), _match_direction (match_direction),
+ _opener (&opener), _format (format), _configured (false)
+ {
+ _stream = create_stream ();
+ }
+
+ imgstream::imgstream (void)
+ : _page (0), _match_direction (false), _opener (NULL), _format (NO_FORMAT),
+ _stream (NULL), _configured (false)
+ {
+ }
+
+ imgstream::~imgstream (void)
+ {
+ delete _stream;
+ }
+
+ imgstream&
+ imgstream::write (const byte_type *data, size_type n)
+ {
+ if (!_stream) return *this;
+
+ if (!_configured)
+ {
+ _stream->size (_h_sz, _v_sz);
+ _stream->resolution (_hres, _vres);
+ _stream->colour (_cspc);
+ _stream->depth (_bits);
+ _configured = true;
+ }
+
+ _stream->write (data, n);
+
+ return *this;
+ }
+
+ imgstream&
+ imgstream::flush (void)
+ {
+ if (_stream) _stream->flush ();
+ return *this;
+ }
+
+ void
+ imgstream::next (void)
+ {
+ if (!_configured) return;
+
+ delete _stream;
+ _configured = false;
+
+ ++_page;
+ ++(*_opener);
+ _stream = create_stream ();
+ if (_match_direction) _stream->rotate_180 (is_back (_page));
+ }
+
+ bool
+ imgstream::is_back (unsigned long page)
+ {
+ return 0 == (page+1)%2;
+ }
+
+ bool
+ imgstream::is_usable (void)
+ {
+ return true;
+ }
+
+ basic_imgstream *
+ imgstream::create_stream (void)
+ {
+ if (PCX == _format) return new pcxstream (*_opener);
+ if (PNM == _format) return new pnmstream (*_opener);
+ if (PNG == _format) return new pngstream (*_opener);
+ if (JPG == _format) return new jpegstream (*_opener);
+ if (PDF == _format) return new pdfstream (*_opener);
+ if (TIF == _format) return new tiffstream (*_opener, _opener->temp ());
+
+ throw std::invalid_argument ("unsupported file format");
+ }
+
+ imgstream *
+ create_imgstream (file_opener& opener, file_format format,
+ bool match_direction)
+ {
+ if (opener.is_collating ())
+ {
+ if (PDF == format) return new pdfstream (opener, match_direction);
+ if (TIF == format) return new tiffstream (opener, opener.name ());
+ }
+
+ return new imgstream (opener, format, match_direction);
+ }
+
+} // namespace iscan
diff --git a/lib/imgstream.hh b/lib/imgstream.hh
new file mode 100644
index 0000000..5904049
--- /dev/null
+++ b/lib/imgstream.hh
@@ -0,0 +1,100 @@
+// imgstream.hh -- provides an interface to write multiple images
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+#ifndef iscan_imgstream_hh_included
+#define iscan_imgstream_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "basic-imgstream.hh"
+#include "file-opener.hh"
+
+
+namespace iscan
+{
+ enum file_format
+ {
+ PCX,
+ PNM,
+ PNG,
+ JPG, JPEG = JPG,
+ PDF,
+ TIF, // libtiff uses TIFF as a type already!
+ NO_FORMAT,
+ };
+
+ class imgstream : public basic_imgstream
+ {
+ public:
+ typedef basic_imgstream::byte_type byte_type;
+ typedef basic_imgstream::size_type size_type;
+
+ imgstream (file_opener& opener, file_format format,
+ bool match_direction = false);
+ virtual ~imgstream (void);
+
+ virtual imgstream& write (const byte_type *data, size_type n);
+ virtual imgstream& flush (void);
+
+ virtual void next (void);
+
+ static bool is_usable (void);
+
+ protected:
+ imgstream (void);
+
+ bool is_back (unsigned long); // indicates whether a page is the
+ // back page of a duplex scan
+
+ unsigned long _page;
+ bool _match_direction; // when true, match front and back
+ // orientation for duplex scans
+
+ private:
+ basic_imgstream * create_stream (void);
+
+ file_opener* _opener;
+ file_format _format;
+
+ basic_imgstream *_stream;
+ bool _configured;
+ };
+
+ imgstream *
+ create_imgstream (file_opener& opener, file_format format,
+ bool match_direction = false);
+
+} // namespace iscan
+
+#endif /* iscan_imgstream_hh_included */
diff --git a/lib/jpegstream.cc b/lib/jpegstream.cc
new file mode 100644
index 0000000..66bf92d
--- /dev/null
+++ b/lib/jpegstream.cc
@@ -0,0 +1,270 @@
+// jpegstream.cc -- image streams producing JPEG files
+// Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "jpegstream.hh"
+
+#include <cstdlib>
+#include <ios>
+
+namespace iscan
+{
+ jpegstream::jpegstream (FILE *fp, const string& name)
+ : _stream (fp), _header (false), _scanline (NULL)
+ {
+ if (!_stream) throw std::invalid_argument ("invalid file handle");
+#if HAVE_JPEGLIB_H
+ init ();
+#endif
+ }
+
+ jpegstream::~jpegstream (void)
+ {
+ delete [] _scanline;
+#if HAVE_JPEGLIB_H
+ if (_header)
+ {
+ if (0 == _v_sz) lib->finish_compress (&_info);
+ lib->destroy_compress (&_info);
+ }
+#endif
+ fflush (_stream);
+ }
+
+ basic_imgstream&
+ jpegstream::write (const byte_type *line, size_type n)
+ {
+ if (!line || 0 == n) return *this;
+#if HAVE_JPEGLIB_H
+ if (!_header)
+ {
+ write_header ();
+ }
+ if (!_scanline)
+ {
+ lib->write_scanlines (&_info, (JSAMPLE **) &line, 1);
+ if (0 < _info.err->msg_code)
+ throw std::ios_base::failure ("write error");
+ }
+ else
+ {
+ // FIXME: assumes that _bits == 1, whereas the condition for
+ // _scanline to be true, see write_init (), requires
+ // only that _bits != 8.
+ for (unsigned int i = 0; i < _h_sz; ++i)
+ {
+ div_t index = div (i, 8 * sizeof (JSAMPLE));
+ int offset = 8 * sizeof (JSAMPLE) - 1 - index.rem;
+ _scanline[i] = ((line[index.quot] & (1 << offset))
+ ? 0 : ~0);
+ }
+ lib->write_scanlines (&_info, (JSAMPLE **) &_scanline, 1);
+ if (0 < _info.err->msg_code)
+ throw std::ios_base::failure ("write error");
+ }
+ --_v_sz;
+#endif /* HAVE_JPEGLIB_H */
+ return *this;
+ }
+
+ bool
+ jpegstream::is_usable (void)
+ {
+ if (lib)
+ {
+ return lib->is_usable;
+ }
+
+ lib = new (std::nothrow) jpeg_lib_handle ();
+ if (!lib)
+ {
+ return false;
+ }
+
+ lib->is_usable = false;
+ lib->message = string ();
+ lib->lib = NULL;
+#if HAVE_JPEGLIB_H
+ try
+ {
+ basic_imgstream::dlopen ("libjpeg", validate);
+ }
+ catch (std::runtime_error& e)
+ {
+ lib->message = e.what ();
+ return lib->is_usable;
+ }
+#endif /* HAVE_JPEGLIB_H */
+
+ return lib->is_usable;
+ }
+
+#if HAVE_JPEGLIB_H
+#define funcsym(name) \
+ lib->name \
+ = ((jpegstream::jpeg_lib_handle::name##_f) \
+ basic_imgstream::dlsym (lib->lib, "jpeg_"#name));
+#endif
+
+ bool
+ jpegstream::validate (lt_dlhandle h)
+ {
+ if (!h) return false;
+
+#if HAVE_JPEGLIB_H
+ lib->lib = h;
+
+# ifndef jpeg_create_compress
+ funcsym (create_compress);
+# else
+ funcsym (CreateCompress);
+# endif
+ funcsym (finish_compress);
+ funcsym (destroy_compress);
+ funcsym (destroy);
+ funcsym (stdio_dest);
+ funcsym (std_error);
+ funcsym (write_scanlines);
+ funcsym (set_defaults);
+ funcsym (start_compress);
+ funcsym (default_qtables);
+
+ // restrict usage of libjpeg to the version range it was compiled against;
+ // either before version 7.0 or 7.0 and later
+ bool is_version_consistent =
+ ((JPEG_LIB_VERSION < 70 && !lib->default_qtables) ||
+ (JPEG_LIB_VERSION >= 70 && lib->default_qtables));
+
+ lib->is_usable = (
+# ifndef jpeg_create_compress
+ lib->create_compress
+# else
+ lib->CreateCompress
+# endif
+ && lib->finish_compress
+ && lib->destroy_compress
+ && lib->destroy
+ && lib->stdio_dest
+ && lib->std_error
+ && lib->write_scanlines
+ && lib->set_defaults
+ && lib->start_compress
+ && is_version_consistent);
+#endif /* HAVE_JPEGLIB_H */
+
+ return lib->is_usable;
+ }
+
+#if HAVE_JPEGLIB_H
+#undef funcsym
+#endif
+
+ basic_imgstream&
+ jpegstream::write_header (void)
+ {
+ check_consistency ();
+
+#if HAVE_JPEGLIB_H
+
+ _info.image_width = _h_sz;
+ _info.image_height = _v_sz;
+
+ _info.in_color_space = (RGB == _cspc ? JCS_RGB : JCS_GRAYSCALE);
+ _info.input_components = (RGB == _cspc ? 3 : 1);
+
+ lib->set_defaults (&_info);
+
+ size_type density_max = (1 << sizeof (_info.X_density) * 8) - 1;
+ _info.density_unit = 1;
+ _info.X_density = (_hres <= density_max) ? _hres : density_max;
+ _info.Y_density = (_vres <= density_max) ? _vres : density_max;
+
+ lib->start_compress (&_info, true);
+
+ if (mono == _cspc && 8 != _bits)
+ {
+ _scanline = new byte_type[_h_sz];
+ }
+ else
+ {
+ _scanline = NULL;
+ }
+#endif /* HAVE_JPEGLIB_H */
+
+ _header = true;
+ return *this;
+ }
+
+ void
+ jpegstream::check_consistency (void) const
+ {
+ if (!(mono == _cspc || grey == _cspc || RGB == _cspc))
+ {
+ throw std::logic_error ("unsupported colour space");
+ }
+ }
+
+ void
+ jpegstream::init (void)
+ {
+ if (!is_usable ())
+ {
+ throw std::runtime_error (lib->message);
+ }
+
+#if HAVE_JPEGLIB_H
+ // set up JPEG library default error handlers first, then override
+ // error handling for fatal errors (because the default would just
+ // end up calling exit())
+ _info.err = lib->std_error (&_err);
+ _err.error_exit = error_exit;
+# ifndef jpeg_create_compress
+ lib->create_compress (&_info);
+# else
+ lib->CreateCompress (&_info, JPEG_LIB_VERSION,
+ (size_t) sizeof (struct jpeg_compress_struct));
+# endif
+ lib->stdio_dest (&_info, _stream);
+#endif /* HAVE_JPEGLIB_H */
+ }
+
+ jpegstream::jpeg_lib_handle *jpegstream::lib = NULL;
+
+#if HAVE_JPEGLIB_H
+ void
+ jpegstream::error_exit (jpeg_common_struct *info)
+ {
+ jpegstream::lib->destroy (info);
+ }
+#endif
+
+} // namespace iscan
diff --git a/lib/jpegstream.hh b/lib/jpegstream.hh
new file mode 100644
index 0000000..855341c
--- /dev/null
+++ b/lib/jpegstream.hh
@@ -0,0 +1,118 @@
+// jpegstream.hh -- image streams producing JPEG files
+// Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifndef iscan_jpegstream_hh_included
+#define iscan_jpegstream_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "basic-imgstream.hh"
+
+#include <cstdio>
+#include <string>
+
+#if HAVE_JPEGLIB_H
+#include <jpeglib.h>
+#endif
+
+namespace iscan
+{
+ using std::string;
+
+ class jpegstream : public basic_imgstream
+ {
+ public:
+ typedef basic_imgstream::byte_type byte_type;
+ typedef basic_imgstream::size_type size_type;
+
+ explicit jpegstream (FILE *fp, const string& pathname = string ());
+ virtual ~jpegstream (void);
+
+ virtual basic_imgstream& write (const byte_type *line, size_type n);
+
+ static bool is_usable (void);
+
+ private:
+ basic_imgstream& write_header (void);
+ void check_consistency (void) const;
+
+ void init (void);
+
+ FILE *_stream;
+ bool _header;
+
+ byte_type *_scanline;
+
+ static bool validate (lt_dlhandle h);
+ struct jpeg_lib_handle
+ {
+ bool is_usable;
+ string message;
+ lt_dlhandle lib;
+
+#if HAVE_JPEGLIB_H
+# ifndef jpeg_create_compress
+ fundecl (void, create_compress, jpeg_compress_struct *);
+# else
+ fundecl (void, CreateCompress, jpeg_compress_struct *, int, size_t);
+# endif
+ fundecl (void, finish_compress, jpeg_compress_struct *);
+ fundecl (void, destroy_compress, jpeg_compress_struct *);
+ fundecl (void, destroy, jpeg_common_struct *);
+
+ fundecl (void, stdio_dest, jpeg_compress_struct *, FILE *);
+ fundecl (struct jpeg_error_mgr *, std_error, jpeg_error_mgr *);
+
+ fundecl (void, write_scanlines, jpeg_compress_struct *, JSAMPLE **, int);
+ fundecl (void, set_defaults, jpeg_compress_struct *);
+ fundecl (void, start_compress, jpeg_compress_struct *, bool);
+
+ // only used for version detection purposes; available since libjpeg 7.0
+ fundecl (void, default_qtables, jpeg_compress_struct *, bool);
+#endif /* HAVE_JPEGLIB_H */
+ };
+ static jpeg_lib_handle *lib;
+
+#if HAVE_JPEGLIB_H
+ static void error_exit (j_common_ptr info);
+
+ struct jpeg_compress_struct _info;
+ struct jpeg_error_mgr _err;
+#endif
+ };
+
+} // namespace iscan
+
+#endif /* !defined (iscan_jpegstream_hh_included) */
diff --git a/lib/pcxstream.cc b/lib/pcxstream.cc
new file mode 100644
index 0000000..722e0b0
--- /dev/null
+++ b/lib/pcxstream.cc
@@ -0,0 +1,320 @@
+// pcxstream.cc -- image streams producing PCX files
+// Copyright (C) 2011 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "pcxstream.hh"
+
+#include <ios>
+#include <cstring>
+
+namespace iscan
+{
+ pcxstream::pcxstream (FILE *fp, const string& name)
+ : _stream (fp),
+ _header (false),
+ _footer (false),
+ _bytesperline (0),
+ _row_buf (NULL),
+ _zbuf (NULL)
+ {
+ if (!_stream) throw std::invalid_argument ("invalid file handle");
+
+ memset (_pcx_header, 0, sizeof (_pcx_header));
+ _pcx_header [0] = 0x0a; // Manufacturer
+ _pcx_header [1] = 0x05; // Version
+ _pcx_header [2] = 1; // Encoding
+ }
+
+ pcxstream::~pcxstream (void)
+ {
+ fflush (_stream);
+ }
+
+ basic_imgstream&
+ pcxstream::flush (void)
+ {
+ if (!_footer)
+ {
+ if (mono == _cspc || grey == _cspc)
+ {
+ write_palette ();
+ }
+ delete [] _row_buf;
+ delete [] _zbuf;
+ _footer = true;
+ }
+
+ fflush (_stream);
+ return *this;
+ }
+
+ void
+ pcxstream::write_palette (void)
+ {
+ const size_type palette_size = 256 * 3 + 1;
+ byte_type palette [palette_size];
+ byte_type *ptr = palette;
+ *ptr = 0x0c; // set palette identifier
+ ++ptr;
+
+ for (int i=0; 256>i; ++i)
+ {
+ *ptr = *(ptr+1) = *(ptr+2) = i;
+ ptr += 3;
+ }
+ size_t rv = fwrite (palette, sizeof (*palette), palette_size, _stream);
+ if (palette_size != rv)
+ {
+ throw std::ios_base::failure ("write error");
+ }
+ }
+
+ basic_imgstream&
+ pcxstream::write (const byte_type *line, size_type n)
+ {
+ if (!line || 0 == n) return *this;
+ if (!_header)
+ {
+ size_type sz = 0;
+ write_header ();
+ if (mono == _cspc)
+ {
+ sz = n * 8;
+ pwrite = &pcxstream::write_mono;
+ }
+ else if (grey == _cspc)
+ {
+ sz = 1; // dummy
+ pwrite = &pcxstream::write_gray;
+ }
+ else if (RGB == _cspc)
+ {
+ sz = _h_sz * 3; // r + g + b
+ pwrite = &pcxstream::write_color;
+ }
+ _row_buf = new byte_type [sz];
+ _zbuf = new byte_type [_h_sz * 2 + 1]; // assume worst case
+ }
+
+ (this->*pwrite) (line, n);
+
+ return *this;
+ }
+
+ void
+ pcxstream::write_color (const byte_type *line, size_type n)
+ {
+ byte_type *r = _row_buf;
+ byte_type *g = r + _h_sz;
+ byte_type *b = g + _h_sz;
+
+ for (size_type i=0; _h_sz>i; ++i)
+ {
+ r [i] = *line; ++line;
+ g [i] = *line; ++line;
+ b [i] = *line; ++line;
+ }
+ write_row (r, _h_sz);
+ write_row (g, _h_sz);
+ write_row (b, _h_sz);
+ }
+
+ void
+ pcxstream::write_gray (const byte_type *line, size_type n)
+ {
+ write_row (line, n);
+ }
+
+ void
+ pcxstream::write_mono (const byte_type *line, size_type n)
+ {
+ for (size_type i=0; n>i; ++i)
+ {
+ for (size_type j=0; 8>j; ++j)
+ {
+ _row_buf [i*8+7-j] = (line[i] & (0x1 << j)) ? 0x0 : 0xff;
+ }
+ }
+ write_row (_row_buf, _h_sz);
+ }
+
+ void
+ pcxstream::write_row (const byte_type *line, size_type n)
+ {
+ size_type zlen = compress_row (line, n, _zbuf);
+
+ // 2 byte align
+ if (n < _bytesperline)
+ {
+ _zbuf [zlen] = 0x00; // add padding
+ ++zlen;
+ }
+
+ write_bytes (_zbuf, zlen);
+ }
+
+ void
+ pcxstream::write_bytes (const byte_type *bytes, size_type n)
+ {
+ size_type rv;
+ rv = fwrite (bytes, sizeof (*bytes), n, _stream);
+ if (n != rv)
+ {
+ throw std::ios_base::failure ("write error");
+ }
+ }
+
+ basic_imgstream::size_type
+ pcxstream::compress_row (const byte_type *line,
+ const size_type n,
+ byte_type *compressed)
+ {
+ byte_type cnt = 0;
+ byte_type *ptr = compressed;
+ byte_type saved = 0;
+
+ for (size_type i=0; i<n; ++i)
+ {
+ if (0 == cnt)
+ {
+ saved = line[i];
+ cnt = 1;
+ }
+ else if (63 > cnt && saved == line[i])
+ {
+ ++cnt;
+ }
+ else if (63 == cnt || saved != line[i])
+ {
+ if (1 < cnt || 0xc0 == (saved & 0xc0))
+ {
+ *ptr = 0xc0 | cnt;
+ ++ptr;
+ }
+ *ptr = saved;
+ ++ptr;
+ saved = line[i];
+ cnt = 1;
+ }
+ }
+ // put the rest
+ if (1 < cnt || 0xc0 == (saved & 0xc0))
+ {
+ *ptr = 0xc0 | cnt;
+ ++ptr;
+ }
+ *ptr = saved;
+ ++ptr;
+
+ return (ptr - compressed);
+ }
+
+ bool
+ pcxstream::is_usable (void)
+ {
+ return true;
+ }
+
+ basic_imgstream&
+ pcxstream::write_header (void)
+ {
+ check_consistency ();
+
+ // Even monochrome image, BitsPerPixel in PCX header is set to 8
+ // in accordance with the implementation of GIMP.
+ size_type bpp = 8;
+ _pcx_header [3] = bpp; // BitsPerPixel
+
+ set_value_le ((_h_sz-1), &_pcx_header [8], 2); // Window.Xmax
+ set_value_le ((_v_sz-1), &_pcx_header [10], 2); // Window.Ymax
+ set_value_le (_hres, &_pcx_header [12], 2); // HDpi
+ set_value_le (_vres, &_pcx_header [14], 2); // VDpi
+
+ size_type nplanes = 1;
+ size_type paletteinfo = 2;
+ if (RGB == _cspc)
+ {
+ nplanes = 3;
+ paletteinfo = 1;
+ }
+ _pcx_header [65] = nplanes; // NPlanes
+ set_value_le (paletteinfo, &_pcx_header [68], 2); // PaletteInfo
+
+ _bytesperline = (_h_sz * bpp + 7) / 8;
+ _bytesperline += (_bytesperline & 0x1); // 2 byte align
+ set_value_le (_bytesperline, &_pcx_header [66], 2); // BytesPerLine
+
+ write_bytes (_pcx_header, sizeof (_pcx_header));
+
+ _header = true;
+ return *this;
+ }
+
+ void
+ pcxstream::check_consistency (void) const
+ {
+ if (!(mono == _cspc || grey == _cspc || RGB == _cspc))
+ {
+ throw std::logic_error ("unsupported colour space");
+ }
+ if (!(_bits == 1 || _bits == 8))
+ {
+ throw std::logic_error ("unsupported bit depth");
+ }
+ size_type max = (1 << 16) - 1;
+ if (_h_sz > max || _v_sz > max)
+ {
+ throw std::logic_error ("maximum image size exceeded");
+ }
+ }
+
+ // set value to array with little endian
+ void
+ pcxstream::set_value_le (const size_type value,
+ byte_type *array,
+ const size_type array_size)
+ {
+ if (!array || 0 >= array_size)
+ {
+ throw std::invalid_argument ("invalid argment");
+ }
+
+ size_type v = value;
+
+ for (size_type i=0; array_size>i; ++i)
+ {
+ array[i] = v & 0xff;
+ v >>= 8;
+ }
+ }
+
+} // namespace iscan
diff --git a/lib/pcxstream.hh b/lib/pcxstream.hh
new file mode 100644
index 0000000..883b0af
--- /dev/null
+++ b/lib/pcxstream.hh
@@ -0,0 +1,93 @@
+// pcxstream.hh -- image streams producing PCX files
+// Copyright (C) 2011 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifndef iscan_pcxstream_hh_included
+#define iscan_pcxstream_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "basic-imgstream.hh"
+
+#include <cstdio>
+#include <string>
+
+namespace iscan
+{
+ using std::string;
+
+ class pcxstream : public basic_imgstream
+ {
+ public:
+ typedef basic_imgstream::byte_type byte_type;
+ typedef basic_imgstream::size_type size_type;
+
+ explicit pcxstream (FILE *fp, const string& name = string ());
+ virtual ~pcxstream (void);
+
+ virtual basic_imgstream& write (const byte_type *line, size_type n);
+ virtual basic_imgstream& flush (void);
+
+ static bool is_usable (void);
+
+ private:
+ basic_imgstream& write_header (void);
+ void write_color (const byte_type *line, size_type n);
+ void write_gray (const byte_type *line, size_type n);
+ void write_mono (const byte_type *line, size_type n);
+ void write_row (const byte_type *line, size_type n);
+ void write_bytes (const byte_type *bytes, size_type n);
+ void write_palette (void);
+ size_type compress_row (const byte_type *line,
+ const size_type n,
+ byte_type *compressed);
+ void check_consistency (void) const;
+ void set_value_le (const size_type value,
+ byte_type *array,
+ const size_type array_size);
+
+ FILE *_stream;
+ bool _header;
+ bool _footer;
+ byte_type _pcx_header [128];
+ size_type _bytesperline;
+ byte_type *_row_buf;
+ byte_type *_zbuf;
+
+ void (pcxstream::*pwrite) (const byte_type *line, size_type n);
+ };
+
+} // namespace iscan
+
+#endif /* !defined (iscan_pcxstream_hh_included) */
diff --git a/lib/pdf/Makefile.am b/lib/pdf/Makefile.am
new file mode 100644
index 0000000..4cc3e95
--- /dev/null
+++ b/lib/pdf/Makefile.am
@@ -0,0 +1,44 @@
+## Makefile.am -- an automake template for a Makefile.in file
+## Copyright (C) 2008 SEIKO EPSON CORPORATION
+##
+## This file is part of the "Image Scan!" build infra-structure.
+##
+## The "Image Scan!" build infra-structure is free software.
+## You can redistribute it and/or modify it under the terms of the GNU
+## General Public License as published by the Free Software Foundation;
+## either version 2 of the License or at your option any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+## FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+## See the GNU General Public License for more details.
+##
+## You should have received a verbatim copy of the GNU General Public
+## License along with this program; if not, write to:
+##
+## Free Software Foundation, Inc.
+## 59 Temple Place, Suite 330
+## Boston, MA 02111-1307 USA
+
+
+if ENABLE_FRONTEND
+noinst_LTLIBRARIES = libpdf.la
+libpdf_la_LDFLAGS = -static
+libpdf_la_SOURCES = \
+ $(libpdf_la_files)
+endif
+
+libpdf_la_files = \
+ array.cc \
+ array.hh \
+ dictionary.cc \
+ dictionary.hh \
+ object.cc \
+ object.hh \
+ primitive.cc \
+ primitive.hh \
+ writer.cc \
+ writer.hh
+
+EXTRA_DIST = \
+ $(libpdf_la_files)
diff --git a/lib/pdf/array.cc b/lib/pdf/array.cc
new file mode 100644
index 0000000..812ee03
--- /dev/null
+++ b/lib/pdf/array.cc
@@ -0,0 +1,115 @@
+// array.cc -- PDF array objects
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "array.hh"
+
+namespace iscan
+{
+
+namespace pdf
+{
+
+array::~array ()
+{
+ store_citer it;
+
+ for (it = _mine.begin (); _mine.end () != it; ++it)
+ {
+ object* obj = *it;
+ delete obj;
+ obj = NULL;
+ }
+}
+
+void
+array::insert (object *value)
+{
+ _store.push_back (value);
+}
+
+void
+array::insert (primitive value)
+{
+ primitive *copy = new primitive ();
+
+ *copy = value;
+ _mine.push_back (copy);
+ insert (copy);
+}
+
+void
+array::insert (object value)
+{
+ object *copy = new object ();
+
+ *copy = value;
+ _mine.push_back (copy);
+ insert (copy);
+}
+
+size_t
+array::size () const
+{
+ return _store.size ();
+}
+
+const object *
+array::operator[] (size_t index) const
+{
+ return _store[index];
+}
+
+void
+array::print (FILE* fp) const
+{
+ store_citer it;
+
+ fprintf (fp, "[ ");
+ if (4 < _store.size ())
+ {
+ fprintf (fp, "\n");
+ }
+ for (it = _store.begin (); _store.end () != it; ++it)
+ {
+ (*it)->print (fp);
+ fprintf (fp, " ");
+ if (4 < _store.size ())
+ {
+ fprintf (fp, "\n");
+ }
+ }
+ fprintf (fp, "]");
+}
+
+} // namespace pdf
+} // namespace iscan
diff --git a/lib/pdf/array.hh b/lib/pdf/array.hh
new file mode 100644
index 0000000..7bb5ac5
--- /dev/null
+++ b/lib/pdf/array.hh
@@ -0,0 +1,87 @@
+// array.hh -- PDF array objects
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+#ifndef iscan_pdf_array_hh_included
+#define iscan_pdf_array_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "object.hh"
+#include "primitive.hh"
+
+#include <vector>
+
+namespace iscan
+{
+
+namespace pdf
+{
+
+/*! Defines a PDF array object [p 58].
+ */
+class array : public object
+{
+private:
+ typedef std::vector<object *> store_type;
+ typedef store_type::iterator store_iter;
+ typedef store_type::const_iterator store_citer;
+
+ store_type _store;
+ store_type _mine;
+
+public:
+ virtual ~array ();
+
+ /*! Insert an object at the end the array
+ */
+ void insert (object* obj);
+
+ void insert (primitive obj);
+ void insert (object obj);
+
+ /*! Count the number of objects in the array
+ */
+ size_t size() const;
+
+ /*! Obtain a reference to an object at a given index
+ */
+ const object* operator[] (size_t index) const;
+
+ virtual void print (FILE* fp) const;
+};
+
+} // namespace pdf
+} // namespace iscan
+
+#endif // iscan_pdf_array_hh_included
diff --git a/lib/pdf/dictionary.cc b/lib/pdf/dictionary.cc
new file mode 100644
index 0000000..e6822e7
--- /dev/null
+++ b/lib/pdf/dictionary.cc
@@ -0,0 +1,123 @@
+// dictionary.cc -- PDF dictionaries
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "dictionary.hh"
+
+namespace iscan
+{
+
+namespace pdf
+{
+
+dictionary::~dictionary ()
+{
+ store_citer it;
+
+ for (it = _mine.begin (); _mine.end () != it; ++it)
+ {
+ object* obj = it->second;
+ delete obj;
+ obj = NULL;
+ }
+}
+
+void
+dictionary::insert (const char *key, object *value)
+{
+ if (_mine.end () != _mine.find (key))
+ {
+ delete _mine[key];
+ }
+ _store[key] = value;
+}
+
+void
+dictionary::insert (const char *key, primitive value)
+{
+ primitive *copy = new primitive ();
+
+ *copy = value;
+ insert (key, copy);
+ _mine[key] = copy;
+}
+
+void
+dictionary::insert (const char *key, object value)
+{
+ object *copy = new object ();
+
+ *copy = value;
+ insert (key, copy);
+ _mine[key] = copy;
+}
+
+size_t
+dictionary::size () const
+{
+ return _store.size ();
+}
+
+const object *
+dictionary::operator[] (const char* key) const
+{
+ store_citer it = _store.find (key);
+
+ return (_store.end () != it ? it->second : NULL);
+}
+
+void
+dictionary::print (FILE* fp) const
+{
+ store_citer it;
+
+ if (1 >= _store.size ())
+ {
+ it = _store.begin ();
+ fprintf (fp, "<< /%s ", it->first);
+ it->second->print (fp);
+ fprintf (fp, " >>");
+ return;
+ }
+
+ fprintf (fp, "<<\n");
+ for (it = _store.begin (); _store.end () != it; ++it)
+ {
+ fprintf (fp, "/%s ", it->first);
+ it->second->print (fp);
+ fprintf (fp, "\n");
+ }
+ fprintf (fp, ">>");
+}
+
+} // namespace pdf
+} // namespace iscan
diff --git a/lib/pdf/dictionary.hh b/lib/pdf/dictionary.hh
new file mode 100644
index 0000000..d20e0d2
--- /dev/null
+++ b/lib/pdf/dictionary.hh
@@ -0,0 +1,93 @@
+// dictionary.hh -- PDF dictionaries
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+
+#ifndef iscan_pdf_dictionary_hh_included
+#define iscan_pdf_dictionary_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "object.hh"
+#include "primitive.hh"
+
+#include <map>
+
+namespace iscan
+{
+
+namespace pdf
+{
+
+/*! Defines a pdf dictionary object [p 59]
+ */
+class dictionary : public object
+{
+private:
+ typedef std::map<const char *, object *> store_type;
+ typedef store_type::iterator store_iter;
+ typedef store_type::const_iterator store_citer;
+
+ store_type _store;
+ store_type _mine;
+
+public:
+ virtual ~dictionary ();
+
+ /*! Insert a key/value pair into the dictionary
+ *
+ * If the key already exists, its value is replaced with the new one.
+ *
+ * The key is written to the PDF file as a name object as defined in the
+ * PDF spec [p. 59]
+ */
+ void insert (const char *key, object *value);
+
+ void insert (const char *key, primitive value);
+ void insert (const char *key, object value);
+
+ /*! Count the number of objects in the dictionary
+ */
+ size_t size () const;
+
+ /*! Obtain a reference to an object with a given key
+ */
+ const object * operator[] (const char *key) const;
+
+ virtual void print (FILE* fp) const;
+};
+
+} // namespace pdf
+} // namespace iscan
+
+#endif // iscan_pdf_dictionary_hh_included
diff --git a/lib/pdf/object.cc b/lib/pdf/object.cc
new file mode 100644
index 0000000..dba25da
--- /dev/null
+++ b/lib/pdf/object.cc
@@ -0,0 +1,103 @@
+// object.cc -- PDF objects
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "object.hh"
+
+#include <stdexcept>
+
+namespace iscan
+{
+
+namespace pdf
+{
+
+
+object::object ()
+{
+ _obj_num = 0;
+}
+
+object::object (size_t num)
+{
+ // FIXME: what if num has already been used?
+ _obj_num = num;
+}
+
+object::~object (void)
+{
+}
+
+size_t
+object::obj_num ()
+{
+ if (65535 == next_obj_num)
+ {
+ throw std::runtime_error ("PDF object number overflow");
+ }
+
+ if (is_direct ())
+ {
+ _obj_num = ++next_obj_num;
+ }
+ return _obj_num;
+}
+
+bool
+object::operator== (object& that) const
+{
+ // FIXME: what if one or both instances have not gotten an object
+ // number yet?
+ return _obj_num == that._obj_num;
+}
+
+void
+object::print (FILE* fp) const
+{
+ fprintf (fp, "%zu 0 R", _obj_num);
+}
+
+bool
+object::is_direct () const
+{
+ return (0 == _obj_num);
+}
+
+size_t object::next_obj_num = 0;
+
+void object::reset_object_numbers ()
+{
+ next_obj_num = 0;
+}
+
+} // namespace pdf
+} // namespace iscan
diff --git a/lib/pdf/object.hh b/lib/pdf/object.hh
new file mode 100644
index 0000000..ef38e85
--- /dev/null
+++ b/lib/pdf/object.hh
@@ -0,0 +1,120 @@
+// object.hh -- PDF objects
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+#ifndef iscan_pdf_object_hh_included
+#define iscan_pdf_object_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <cstdlib>
+#include <cstdio>
+
+namespace iscan
+{
+
+namespace pdf
+{
+
+using namespace std;
+
+/*! A base class for all pdf objects [p 51].
+ *
+ * A pdf::object is also used to pass around object numbers in a transparent
+ * fashion so that object references can be output correctly as elements of
+ * arrays and dictionaries.
+ */
+class object
+{
+private:
+
+ size_t _obj_num;
+ static size_t next_obj_num; // the next free object number
+
+public:
+
+ object ();
+
+ /*! Creates a new pdf::object with its object number set to \a num.
+ *
+ * Constructs an indirect object.
+ */
+ object (size_t num);
+
+ virtual ~object (void);
+
+ /*! Obtain the pdf::object's object number.
+ *
+ * If the object has not been allocated an object number yet, a new one is
+ * allocated and returned.
+ */
+ size_t obj_num ();
+
+ /*! Determine whether the object is direct or indirect [p 63].
+ *
+ * Probably don't need this method.
+ *
+ * @return True if the object is direct, False if it is indirect
+ */
+ bool is_direct () const;
+
+ /*! Output the object contents.
+ *
+ * In the case of pdf::object, this only outputs an indirect reference to
+ * itself [p 64].
+ *
+ * Each subclass re-implements this in order to print its own content.
+ * It should only ever output the object contents, ommitting the object
+ * definition header and footer [p 64]
+ */
+ virtual void print (FILE* fp) const;
+
+ /*! Compare the contents of two pdf::objects.
+ *
+ * Only the object contents are compared, the object number of the two
+ * objects can be different.
+ *
+ * In the case of pdf::object, the object numbers are compared.
+ */
+ virtual bool operator== (object& that) const;
+
+ /*! Reset the current object number to recycle them for new documents
+ */
+ static void reset_object_numbers ();
+
+};
+
+} // namespace pdf
+} // namespace iscan
+
+#endif // iscan_pdf_object_hh_included
diff --git a/lib/pdf/primitive.cc b/lib/pdf/primitive.cc
new file mode 100644
index 0000000..8a47c14
--- /dev/null
+++ b/lib/pdf/primitive.cc
@@ -0,0 +1,100 @@
+// primitive.cc -- PDF primitives
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "primitive.hh"
+
+#include <sstream>
+
+namespace iscan
+{
+
+namespace pdf
+{
+
+primitive::primitive ()
+ : _str (string ())
+{
+}
+
+primitive::primitive (string value)
+ : _str (value)
+{
+}
+
+primitive::primitive (int value)
+{
+ stringstream ss;
+ ss << value;
+ ss >> _str;
+}
+
+primitive::primitive (size_t value)
+{
+ stringstream ss;
+ ss << value;
+ ss >> _str;
+}
+
+primitive::primitive (double value)
+{
+ stringstream ss;
+ ss << value;
+ ss >> _str;
+}
+
+primitive::~primitive (void)
+{
+}
+
+bool
+primitive::operator== (const primitive& that) const
+{
+ return _str == that._str;
+}
+
+void
+primitive::print (FILE* fp) const
+{
+ fprintf (fp, "%s", _str.c_str ());
+}
+
+// FIXME: doesn't do the default assignment just what we want?
+void
+primitive::operator= (const primitive& that)
+{
+ _str = that._str;
+}
+
+
+} // namespace pdf
+} // namespace iscan
diff --git a/lib/pdf/primitive.hh b/lib/pdf/primitive.hh
new file mode 100644
index 0000000..e45adc0
--- /dev/null
+++ b/lib/pdf/primitive.hh
@@ -0,0 +1,92 @@
+// primitive.hh -- PDF primitives
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+#ifndef iscan_pdf_primitive_hh_included
+#define iscan_pdf_primitive_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "object.hh"
+
+#include <string>
+
+namespace iscan
+{
+
+namespace pdf
+{
+ using std::string;
+
+/*! Defines a primitive pdf object: one of string, name, integer, or real.
+ */
+class primitive : public object
+{
+private:
+ string _str;
+
+public:
+ primitive ();
+
+ /*! Create a new pdf string/name object with the given value.
+ */
+ primitive (string value);
+
+ /*! Create a new pdf integer object
+ */
+ primitive (int value);
+
+ primitive (size_t value);
+
+ /*! Create a new pdf real object
+ */
+ primitive (double value);
+
+ virtual ~primitive (void);
+
+ /*! Compare the contents of two pdf::primitives.
+ *
+ * Only the object contents are compared, the object number of the
+ * two objects may differ.
+ */
+ virtual bool operator== (const primitive& other) const;
+
+ void operator= (const primitive& that);
+
+ virtual void print (FILE* fp) const;
+};
+
+} // namespace pdf
+} // namespace iscan
+
+#endif // iscan_pdf_primitive_hh_included
diff --git a/lib/pdf/writer.cc b/lib/pdf/writer.cc
new file mode 100644
index 0000000..78a9355
--- /dev/null
+++ b/lib/pdf/writer.cc
@@ -0,0 +1,236 @@
+// writer.cc -- putting PDF object in a file
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "writer.hh"
+
+#include <sstream>
+#include <stdexcept>
+
+namespace iscan
+{
+
+namespace pdf
+{
+ using std::runtime_error;
+ using std::string;
+
+writer::writer (FILE *file)
+ : _xref (xref ()), _file (file)
+{
+ _xref_pos = 0;
+ _last_xref_pos = 0;
+ _saved_pos = 0;
+ _mode = object_mode;
+ _stream_len_obj = NULL;
+}
+
+writer::~writer ()
+{
+ delete _stream_len_obj;
+ _stream_len_obj = NULL;
+}
+
+void
+writer::write (object& obj)
+{
+ if (object_mode != _mode)
+ {
+ throw runtime_error ("invalid call to pdf::writer::write (object&)");
+ }
+
+ _xref[obj.obj_num ()] = ftell (_file);
+
+ fprintf (_file, "%zu 0 obj\n", obj.obj_num ());
+ obj.print (_file);
+ fprintf (_file, "\n");
+ fprintf (_file, "endobj\n");
+}
+
+void
+writer::begin_stream (dictionary& dict)
+{
+ if (stream_mode == _mode)
+ {
+ throw runtime_error ("invalid call to pdf::writer::begin_stream ()");
+ }
+ _mode = stream_mode;
+
+ _stream_len_obj = new primitive ();
+ dict.insert ("Length", object (_stream_len_obj->obj_num ()));
+
+ _xref[dict.obj_num ()] = ftell (_file);
+
+ fprintf (_file, "%zu 0 obj\n", dict.obj_num ());
+ dict.print (_file);
+ fprintf (_file, "\n");
+ fprintf (_file, "stream\n");
+
+ _saved_pos = ftell (_file);
+}
+
+void
+writer::write (const char *buf, size_t n)
+{
+ if (stream_mode != _mode)
+ {
+ throw runtime_error ("invalid call to pdf::writer::write ()");
+ }
+ size_t rv = fwrite (buf, sizeof (char), n, _file);
+ if (rv != n) throw std::ios_base::failure ("write error");
+}
+
+void
+writer::write (const string& s)
+{
+ if (stream_mode != _mode)
+ {
+ throw runtime_error ("invalid call to pdf::writer::write ()");
+ }
+ fprintf (_file, "%s", s.c_str ());
+}
+
+void
+writer::end_stream ()
+{
+ if (stream_mode != _mode)
+ {
+ throw runtime_error ("invalid call to pdf::writer::end_stream ()");
+ }
+ _mode = object_mode;
+
+ size_t pos = ftell (_file);
+ size_t length = pos - _saved_pos;
+
+ fprintf (_file, "\n");
+ fprintf (_file, "endstream");
+ fprintf (_file, "\n");
+ fprintf (_file, "endobj");
+ fprintf (_file, "\n");
+
+ // FIXME: overload the '=' operator in pdf::primitive
+ *_stream_len_obj = primitive (length);
+
+ write (*_stream_len_obj);
+ delete _stream_len_obj;
+ _stream_len_obj = NULL;
+}
+
+void
+writer::header ()
+{
+ if (_mode == stream_mode)
+ {
+ throw runtime_error ("cannot write header in stream mode");
+ }
+ fprintf (_file, "%%PDF-1.0\n");
+}
+
+void
+writer::trailer (dictionary& trailer_dict)
+{
+ if (_mode == stream_mode)
+ {
+ throw runtime_error ("cannot write trailer in stream mode");
+ }
+ write_xref ();
+ write_trailer (trailer_dict);
+}
+
+// FIXME: clean up this kludge
+void
+writer::write_xref ()
+{
+ xref::const_iterator it;
+
+ _last_xref_pos = _xref_pos;
+ _xref_pos = ftell (_file);
+
+ fprintf (_file, "xref\n");
+
+ stringstream ss;
+ size_t start_obj_num = 0;
+ size_t cur_obj_num = 0;
+ size_t last_obj_num = 0;
+
+ ss << "0000000000 65535 f " << endl;
+ for(it = _xref.begin (); _xref.end () != it; ++it)
+ {
+ cur_obj_num = it->first;
+
+ if (cur_obj_num != last_obj_num + 1)
+ {
+ // write out the current xref section and start a new one
+ fprintf (_file, "%zu %zu\n", start_obj_num,
+ last_obj_num + 1 - start_obj_num);
+ fprintf (_file, "%s", ss.str ().c_str ());
+
+ ss.str (""); // flush stream
+ start_obj_num = cur_obj_num;
+ }
+
+ last_obj_num = cur_obj_num;
+
+ ss.width (10);
+ ss.fill ('0');
+ ss << it->second << " 00000 n " << endl;
+ }
+
+ if (!ss.str ().empty ())
+ {
+ fprintf (_file, "%zu %zu\n", start_obj_num,
+ last_obj_num + 1 - start_obj_num);
+ fprintf (_file, "%s", ss.str ().c_str ());
+ }
+}
+
+void
+writer::write_trailer (dictionary& trailer_dict)
+{
+ trailer_dict.insert ("Size", primitive (_xref.size () + 1));
+ if (_last_xref_pos != 0)
+ {
+ trailer_dict.insert ("Prev", primitive (_last_xref_pos));
+ }
+
+ fprintf (_file, "trailer\n");
+ trailer_dict.print (_file);
+ fprintf (_file, "\n");
+ fprintf (_file, "startxref\n");
+ fprintf (_file, "%zu\n", _xref_pos);
+ fprintf (_file, "%%%%EOF\n");
+
+ _xref.clear ();
+}
+
+} // namespace pdf
+} // namespace iscan
diff --git a/lib/pdf/writer.hh b/lib/pdf/writer.hh
new file mode 100644
index 0000000..f62c471
--- /dev/null
+++ b/lib/pdf/writer.hh
@@ -0,0 +1,165 @@
+// writer.hh -- putting PDF objects in a file
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+#ifndef iscan_pdf_writer_hh_included
+#define iscan_pdf_writer_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "object.hh"
+#include "primitive.hh"
+#include "dictionary.hh"
+
+#include <map>
+
+namespace iscan
+{
+
+namespace pdf
+{
+ using std::string;
+
+/*! Writes PDF objects to a file.
+ * See section 3.4 of the PDF Reference version 1.7 for details on the basic
+ * file structure of a PDF file.
+ *
+ * There are two writing modes: object mode and stream mode.
+ * The default mode is object mode. When begin_stream() is called, the current
+ * mode is set to stream mode. When end_stream() is called, the mode is reset
+ * to object mode.
+ *
+ * In object mode, PDF objects are written all at once. Stream mode allows the
+ * writing of PDF stream objects which can be written incrementally.
+ */
+class writer
+{
+private:
+ typedef std::map<size_t, size_t> xref;
+
+ xref _xref;
+ size_t _xref_pos;
+ size_t _last_xref_pos;
+
+ FILE *_file;
+ size_t _saved_pos;
+
+ primitive* _stream_len_obj;
+
+ typedef enum {
+ object_mode,
+ stream_mode
+ } write_mode;
+
+ write_mode _mode;
+
+public:
+ /*! Creates a new pdf::writer object which will write to the given stream.
+ */
+ writer (FILE *file);
+
+ ~writer ();
+
+ /*! Writes a pdf::object to the file as an indirect object [p 63].
+ *
+ * Recursively writes any child objects to the file.
+ * Can only be called while in object mode, which is the default
+ * mode. If this method is called between calls to begin_stream() and
+ * end_stream() a std::runtime_error exception is thrown.
+ */
+ void write (object& object);
+
+ /*! Initializes a PDF stream [p 60] and sets the current mode to stream mode.
+ *
+ * Pass a pdf::dictionary object, \a dict, defining the stream properties,
+ * omitting the mandatory "Length" property which is automatically
+ * calculated and written to the file. If this method is called when already
+ * in stream mode, a std::runtime_error exception is thrown.
+ */
+ void begin_stream (dictionary& dict);
+
+ /*! Writes \a n bytes from \a buf to the file as part of a PDF stream.
+ *
+ * Can only be called while in stream mode.
+ * Calls to this method must be contained within calls to
+ * begin_stream() and end_stream(). If not, a std::runtime_error
+ * exception is thrown.
+ */
+ void write (const char* buf, size_t n);
+
+ /*! Writes a string \a s to the file as part of a PDF stream.
+ *
+ * Can only be called while in stream mode.
+ * Calls to this method must be contained within calls to
+ * begin_stream() and end_stream(). If not, a std::runtime_error
+ * exception is thrown.
+ */
+ void write (const string& s);
+
+ /*! Finishes writing a PDF stream and sets the current mode to object mode.
+ *
+ * The "Length" property of the stream is written at this point as an
+ * indirect object. Can only be called when in stream mode, if not, a
+ * std::runtime_error exception is thrown.
+ */
+ void end_stream ();
+
+ /*! Writes the PDF header [p 92].
+ *
+ * A std::runtime_error exception is thrown if this method is called while
+ * in stream mode.
+ */
+ void header ();
+
+ /*! Writes the PDF trailer [p 96] and xref table [p 93].
+ *
+ * Also sets the "Prev" entry in \a trailer_dict and clears the internal
+ * xref table. A std::runtime_error exception is thrown if this method is
+ * called while in stream mode.
+ *
+ * \param trailer_dict The trailer entries to be written.
+ */
+ void trailer (dictionary& trailer_dict);
+
+private:
+ // Writes the cross-reference table [p 93].
+ void write_xref ();
+
+ // Writes the file trailer [p 96].
+ void write_trailer (dictionary& trailer_dict);
+};
+
+} // namespace pdf
+} // namespace iscan
+
+#endif // iscan_pdf_writer_hh_included
diff --git a/lib/pdfstream.cc b/lib/pdfstream.cc
new file mode 100644
index 0000000..4875de5
--- /dev/null
+++ b/lib/pdfstream.cc
@@ -0,0 +1,351 @@
+// pdfstream.cc -- image streams producing PDF files
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "pdfstream.hh"
+#include "jpegstream.hh"
+
+#include <sstream>
+
+namespace iscan
+{
+
+pdfstream::pdfstream (FILE *file, bool match_direction)
+ : imgstream (), // avoid recursion
+ _file (file), _g3 (NULL), _rotate_180 (false)
+{
+ _match_direction = match_direction;
+ init ();
+}
+
+void
+pdfstream::init ()
+{
+ if (!is_usable ())
+ {
+ throw std::runtime_error ("pdf not usable");
+ }
+
+ _page = 0;
+ _row = 0;
+ _need_page_trailer = false;
+ _data_size = 0;
+ _pdf_h_sz = 0;
+ _pdf_v_sz = 0;
+ _do_jpeg = false;
+
+ _doc = NULL;
+ _pages = NULL;
+ _page_list = NULL;
+ _trailer = NULL;
+ _img_height_obj = NULL;
+ _stream = NULL;
+
+ _doc = new pdf::writer (_file);
+
+ pdf::object::reset_object_numbers ();
+}
+
+pdfstream::~pdfstream ()
+{
+ if (_need_page_trailer)
+ {
+ write_page_trailer ();
+ }
+
+ fflush (_file);
+
+ delete _doc;
+ delete _pages;
+ delete _page_list;
+ delete _trailer;
+ delete _img_height_obj;
+
+ delete _stream;
+
+ delete _g3;
+}
+
+bool
+pdfstream::is_usable ()
+{
+ return true;
+}
+
+void
+pdfstream::rotate_180 (bool yes)
+{
+ _rotate_180 = yes;
+}
+
+void
+pdfstream::next ()
+{
+ if (0 == _row)
+ {
+ return;
+ }
+
+ write_page_trailer ();
+ _row = 0;
+ _rotate_180 = _match_direction && is_back (_page);
+}
+
+imgstream&
+pdfstream::write (const byte_type *line, size_type n)
+{
+ if (!line || 0 == n) return *this;
+ if (0 == _page)
+ {
+ write_header ();
+ }
+ if (0 == _row)
+ {
+ // adjust to PDF coordinate system (is this correct?)
+ _pdf_h_sz = (72 * _h_sz) / _hres;
+ _pdf_v_sz = (72 * _v_sz) / _vres;
+
+ write_page_header ();
+ ++_page;
+ }
+
+ if (_stream)
+ {
+ _stream->write (line, n);
+ }
+ else if (_g3)
+ {
+ string buf = (*_g3) (line, n);
+ _doc->write (buf.data (), buf.size ());
+ }
+ else
+ {
+ _doc->write (line, n);
+ }
+ ++_row;
+
+ return *this;
+}
+
+void
+pdfstream::write_header ()
+{
+ _doc->header ();
+
+ delete _pages;
+ _pages = new pdf::dictionary ();
+
+ pdf::dictionary info;
+ info.insert ("Producer", pdf::primitive ("(iscan)"));
+ info.insert ("Creator", pdf::primitive ("(iscan)"));
+ _doc->write (info);
+
+ pdf::dictionary catalog;
+ catalog.insert ("Type", pdf::primitive ("/Catalog"));
+ catalog.insert ("Pages", pdf::object (_pages->obj_num ()));
+ _doc->write (catalog);
+
+ delete _trailer;
+ _trailer = new pdf::dictionary ();
+ _trailer->insert ("Info", pdf::object (info.obj_num ()));
+ _trailer->insert ("Root", pdf::object (catalog.obj_num ()));
+
+ delete _page_list;
+ _page_list = new pdf::array ();
+}
+
+void
+pdfstream::write_page_header ()
+{
+ pdf::dictionary page;
+
+ _page_list->insert (pdf::object (page.obj_num ()));
+
+ _pages->insert ("Type", pdf::primitive ("/Pages"));
+ _pages->insert ("Kids", _page_list);
+ _pages->insert ("Count", pdf::primitive (_page_list->size ()));
+
+ _doc->write (*_pages);
+
+ pdf::dictionary image;
+ pdf::dictionary contents;
+
+ pdf::array mbox;
+ mbox.insert (pdf::primitive (0));
+ mbox.insert (pdf::primitive (0));
+ mbox.insert (pdf::primitive (_pdf_h_sz));
+ mbox.insert (pdf::primitive (_pdf_v_sz));
+
+ std::stringstream ss2;
+ std::string img_name;
+
+ ss2 << "iscanImage" << _page;
+ img_name = ss2.str ();
+
+ pdf::array procset;
+ std::string cproc = "/ImageB";
+ if (RGB == _cspc)
+ {
+ cproc = "/ImageC";
+ }
+ pdf::dictionary tmp;
+ tmp.insert (img_name.c_str (), pdf::object (image.obj_num ()));
+
+ procset.insert (pdf::primitive ("/PDF"));
+ procset.insert (pdf::primitive (cproc));
+
+ pdf::dictionary rsrc;
+ rsrc.insert ("XObject", &tmp);
+ rsrc.insert ("ProcSet", &procset);
+
+ page.insert ("Type", pdf::primitive ("/Page"));
+ page.insert ("Parent", pdf::object (_pages->obj_num ()));
+ page.insert ("Resources", &rsrc);
+ page.insert ("MediaBox", &mbox);
+ page.insert ("Contents", pdf::object (contents.obj_num ()));
+
+ _doc->write (page);
+
+ _doc->begin_stream (contents);
+
+ // transformation matrices must be specified in reverse order
+ std::stringstream ss;
+ ss << "q" << std::endl;
+ ss << _pdf_h_sz << " 0 0 " << _pdf_v_sz << " 0 0 cm" << std::endl;
+ if (_rotate_180)
+ {
+ // undo the translation below
+ ss << "1 0 0 1 0.5 0.5 cm" << std::endl;
+
+ // reflect along x and y axis
+ ss << "-1 0 0 -1 0 0 cm" << std::endl;
+
+ // translate so the image midpoint lies on the origin
+ ss << "1 0 0 1 -0.5 -0.5 cm" << std::endl;
+ }
+ ss << "/" << img_name << " Do" << std::endl;
+ ss << "Q";
+
+ _doc->write (ss.str ());
+ _doc->end_stream ();
+
+ write_image_object (image, img_name);
+
+ _need_page_trailer = true;
+}
+
+void
+pdfstream::write_image_object (pdf::dictionary& image, std::string name)
+{
+ delete _img_height_obj;
+ _img_height_obj = new pdf::primitive ();
+
+ image.insert ("Type", pdf::primitive ("/XObject"));
+ image.insert ("Subtype", pdf::primitive ("/Image"));
+ image.insert ("Width", pdf::primitive (_h_sz));
+ image.insert ("Height", pdf::object (_img_height_obj->obj_num ()));
+
+ pdf::array decode;
+ std::string dev = "/DeviceGray";
+ if (RGB == _cspc) dev = "/DeviceRGB";
+
+ if (monochrome == _cspc)
+ {
+ if (!_g3) _g3 = new fax_encoder;
+ }
+ else
+ {
+ _do_jpeg = iscan::jpegstream::is_usable ();
+ }
+ image.insert ("ColorSpace", pdf::primitive (dev));
+ image.insert ("BitsPerComponent", pdf::primitive (_bits));
+ image.insert ("Interpolate", pdf::primitive ("true"));
+
+ pdf::dictionary parms;
+ if (_do_jpeg)
+ {
+ image.insert ("Filter", pdf::primitive ("/DCTDecode"));
+ }
+ else if (_g3)
+ {
+ image.insert ("Filter", pdf::primitive ("/CCITTFaxDecode"));
+
+ parms.insert ("Columns", pdf::primitive (_h_sz));
+ parms.insert ("Rows", pdf::primitive (_v_sz));
+ parms.insert ("EndOfBlock", pdf::primitive ("false"));
+ parms.insert ("EndOfLine", pdf::primitive ("true"));
+ parms.insert ("EncodedByteAlign", pdf::primitive ("true"));
+ parms.insert ("K", pdf::primitive (0)); // CCITT3 1-D encoding
+ image.insert ("DecodeParms", &parms);
+ }
+
+ // see PDF reference 1.7 p. 342 and p. 1107 # 53
+ image.insert ("Name", pdf::primitive ("/" + name));
+
+ _doc->begin_stream (image);
+
+ if (_do_jpeg)
+ {
+ _stream = new jpegstream (_file);
+ }
+
+ if (_stream)
+ {
+ _stream->size (_h_sz, _v_sz);
+ _stream->depth (_bits);
+ _stream->colour (_cspc);
+ _stream->resolution (_hres, _vres);
+ }
+}
+
+void
+pdfstream::write_page_trailer ()
+{
+ delete _stream;
+ _stream = NULL;
+
+ _doc->end_stream ();
+
+ *_img_height_obj = pdf::primitive (_row);
+ _doc->write (*_img_height_obj);
+
+ _doc->trailer (*_trailer);
+
+ _need_page_trailer = false;
+
+ _pdf_h_sz = 0;
+ _pdf_v_sz = 0;
+
+ _do_jpeg = false;
+}
+
+} // namespace iscan
diff --git a/lib/pdfstream.hh b/lib/pdfstream.hh
new file mode 100644
index 0000000..70f7069
--- /dev/null
+++ b/lib/pdfstream.hh
@@ -0,0 +1,105 @@
+// pdfstream.hh -- image streams producing PDF files
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+#ifndef iscan_pdfstream_hh_included
+#define iscan_pdfstream_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "imgstream.hh"
+
+#include "pdf/writer.hh"
+#include "pdf/dictionary.hh"
+#include "pdf/array.hh"
+#include "pdf/primitive.hh"
+#include "fax-encoder.hh"
+
+#include <string>
+
+namespace iscan
+{
+ using std::string;
+
+class pdfstream : public imgstream
+{
+
+public:
+ typedef basic_imgstream::byte_type byte_type;
+ typedef basic_imgstream::size_type size_type;
+
+private:
+ bool _need_page_trailer;
+ size_type _data_size;
+ size_type _pdf_h_sz; // scaled size to fit on a page at 72 dpi
+ size_type _pdf_v_sz; // but better to set the dpi in the PDF file,
+ // there is some way to do that, I read it in the spec!
+
+ pdf::writer *_doc;
+ pdf::dictionary *_pages;
+ pdf::array *_page_list;
+ pdf::dictionary *_trailer;
+
+ size_type _row;
+ pdf::primitive *_img_height_obj;
+
+ basic_imgstream *_stream;
+ FILE *_file;
+
+ bool _do_jpeg;
+ fax_encoder *_g3;
+ bool _rotate_180;
+
+public:
+ static bool is_usable ();
+
+ explicit pdfstream (FILE *fp, bool match_direction = false);
+
+ virtual ~pdfstream ();
+
+ virtual imgstream& write (const byte_type *line, size_type n);
+
+ virtual void next ();
+ virtual void rotate_180 (bool yes);
+
+private:
+ void init();
+ void write_header ();
+ void write_page_header ();
+ void write_page_trailer ();
+ void write_image_object (pdf::dictionary& image, std::string name);
+};
+
+} // namespace iscan
+
+#endif /* iscan_pdfstream_hh_included */
diff --git a/lib/pngstream.cc b/lib/pngstream.cc
new file mode 100644
index 0000000..d9344b4
--- /dev/null
+++ b/lib/pngstream.cc
@@ -0,0 +1,257 @@
+// pngstream.cc -- image streams producing PNG files
+// Copyright (C) 2008, 2009, 2016 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "pngstream.hh"
+
+#include <iostream>
+
+namespace iscan
+{
+ pngstream::pngstream (FILE *fp, const string& name)
+ : _stream (fp), _header (false), _footer (false)
+ {
+ if (!_stream) throw std::invalid_argument ("invalid file handle");
+#if HAVE_PNG_H
+ init ();
+#endif
+ }
+
+ pngstream::~pngstream (void)
+ {
+ try
+ {
+ flush ();
+ }
+ catch (const std::exception& oops)
+ {
+ std::cerr << oops.what ();
+ }
+
+#if HAVE_PNG_H
+ lib->destroy_write_struct (&_png, &_info);
+#endif
+ }
+
+ basic_imgstream&
+ pngstream::write (const byte_type *line, size_type n)
+ {
+ if (!line || 0 == n) return *this;
+#if HAVE_PNG_H
+ if (!_header)
+ {
+ write_header ();
+ }
+ set_error_handler (_png, _info);
+ lib->write_row (_png, (png_byte *) line);
+#endif
+ return *this;
+ }
+
+ basic_imgstream&
+ pngstream::flush (void)
+ {
+#if HAVE_PNG_H
+ set_error_handler (_png, _info);
+
+ if (_header && !_footer && _png->num_rows == _png->flush_rows)
+ {
+ lib->write_end (_png, _info);
+ _footer = true;
+ }
+#endif
+ fflush (_stream);
+
+ return *this;
+ }
+
+ bool
+ pngstream::is_usable (void)
+ {
+ if (lib)
+ {
+ return lib->is_usable;
+ }
+
+ lib = new (std::nothrow) png_lib_handle ();
+ if (!lib)
+ {
+ return false;
+ }
+
+ lib->is_usable = false;
+ lib->message = string ();
+ lib->lib = NULL;
+#if HAVE_PNG_H
+ try
+ {
+ // Some PNG libraries are *not* linked with libz which leads
+ // to a "file not found" error when calling dlopen. We just
+ // dlopen the libz library explicitly to work around this.
+ basic_imgstream::dlopen ("libz");
+ basic_imgstream::dlopen ("libpng12", validate);
+ }
+ catch (std::runtime_error& e)
+ {
+ lib->message = e.what ();
+ return lib->is_usable;
+ }
+#endif /* HAVE_PNG_H */
+
+ return lib->is_usable;
+ }
+
+#if HAVE_PNG_H
+#define funcsym(name) \
+ lib->name \
+ = ((pngstream::png_lib_handle::name##_f) \
+ basic_imgstream::dlsym (lib->lib, "png_"#name));
+#endif
+
+ bool
+ pngstream::validate (lt_dlhandle h)
+ {
+ if (!h) return false;
+
+#if HAVE_PNG_H
+ lib->lib = h;
+
+ funcsym (access_version_number);
+ funcsym (create_write_struct);
+ funcsym (create_info_struct);
+ funcsym (destroy_write_struct);
+ funcsym (init_io);
+ funcsym (set_IHDR);
+ funcsym (set_pHYs);
+ funcsym (set_invert_mono);
+ funcsym (write_info);
+ funcsym (write_row);
+ funcsym (write_flush);
+ funcsym (write_end);
+
+ if (lib->access_version_number
+ && lib->create_write_struct
+ && lib->create_info_struct
+ && lib->destroy_write_struct
+ && lib->init_io
+ && lib->set_IHDR
+ && lib->set_pHYs
+ && lib->set_invert_mono
+ && lib->write_info
+ && lib->write_row
+ && lib->write_flush
+ && lib->write_end)
+ {
+ lib->is_usable = (PNG_LIBPNG_VER <= lib->access_version_number ());
+ }
+#endif /* HAVE_PNG_H */
+
+ return lib->is_usable;
+ }
+
+#if HAVE_PNG_H
+#undef funcsym
+#endif
+
+ basic_imgstream&
+ pngstream::write_header (void)
+ {
+ check_consistency ();
+
+#if HAVE_PNG_H
+ set_error_handler (_png, _info);
+
+ if (mono == _cspc)
+ {
+ _bits = 1;
+ lib->set_invert_mono (_png);
+ }
+
+ lib->init_io (_png, _stream);
+
+ lib->set_IHDR (_png, _info, _h_sz, _v_sz, _bits,
+ (RGB == _cspc
+ ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_GRAY),
+ PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
+
+ size_type hres = size_type (100 / 2.54 * _hres + 0.5);
+ size_type vres = size_type (100 / 2.54 * _vres + 0.5);
+ lib->set_pHYs (_png, _info, hres, vres, PNG_RESOLUTION_METER);
+
+ lib->write_info (_png, _info);
+#endif /* HAVE_PNG_H */
+
+ _header = true;
+ return *this;
+ }
+
+ void
+ pngstream::check_consistency (void) const
+ {
+ if (!(mono == _cspc || grey == _cspc || RGB == _cspc))
+ {
+ throw std::logic_error ("unsupported colour space");
+ }
+ }
+
+ void
+ pngstream::init (void)
+ {
+ if (!is_usable ())
+ {
+ throw std::runtime_error (lib->message);
+ }
+
+#if HAVE_PNG_H
+ _png = lib->create_write_struct (version_string,
+ NULL, NULL, NULL);
+ _info = NULL;
+ if (!_png) throw std::bad_alloc ();
+
+ _info = lib->create_info_struct (_png);
+ if (!_info)
+ {
+ lib->destroy_write_struct (&_png, NULL);
+ throw std::bad_alloc ();
+ }
+#endif /* HAVE_PNG_H */
+ }
+
+ pngstream::png_lib_handle *pngstream::lib = NULL;
+
+#if HAVE_PNG_H
+ png_const_charp pngstream::version_string = PNG_LIBPNG_VER_STRING;
+#endif
+
+} // namespace iscan
diff --git a/lib/pngstream.hh b/lib/pngstream.hh
new file mode 100644
index 0000000..77d4f54
--- /dev/null
+++ b/lib/pngstream.hh
@@ -0,0 +1,145 @@
+// pngstream.hh -- image streams producing PNG files
+// Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+//
+// This file is part of 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifndef iscan_pngstream_hh_included
+#define iscan_pngstream_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "basic-imgstream.hh"
+
+#include <cstdio>
+#include <string>
+#include <ios>
+
+#if HAVE_PNG_H
+#include <png.h>
+#endif
+
+namespace iscan
+{
+ using std::string;
+
+ class pngstream : public basic_imgstream
+ {
+ public:
+ typedef basic_imgstream::byte_type byte_type;
+ typedef basic_imgstream::size_type size_type;
+
+ explicit pngstream (FILE *fp, const string& name = string ());
+ virtual ~pngstream (void);
+
+ virtual basic_imgstream& write (const byte_type *line, size_type n);
+ virtual basic_imgstream& flush (void);
+
+ static bool is_usable (void);
+
+ private:
+ basic_imgstream& write_header (void);
+ void check_consistency (void) const;
+
+ void init (void);
+
+ FILE *_stream;
+ bool _header;
+ bool _footer;
+
+ static bool validate (lt_dlhandle h);
+ struct png_lib_handle
+ {
+ bool is_usable;
+ string message;
+ lt_dlhandle lib;
+
+#if HAVE_PNG_H
+ fundecl (png_uint_32, access_version_number,
+ void);
+ fundecl (png_structp, create_write_struct,
+ png_const_charp, png_voidp, png_error_ptr, png_error_ptr);
+ fundecl (png_infop , create_info_struct ,
+ png_structp);
+ fundecl (void, destroy_write_struct,
+ png_structpp, png_infopp);
+ fundecl (void, init_io,
+ png_structp, FILE *);
+ fundecl (void, set_IHDR,
+ png_structp, png_infop, png_uint_32, png_uint_32, int,
+ int, int, int, int);
+ fundecl (void, set_pHYs,
+ png_structp, png_infop, png_uint_32, png_uint_32, int);
+ fundecl (void, set_invert_mono, png_structp);
+ fundecl (void, write_info,
+ png_structp, png_infop);
+ fundecl (void, write_row,
+ png_structp, png_bytep);
+ fundecl (void, write_flush,
+ png_structp);
+ fundecl (void, write_end,
+ png_structp, png_infop);
+#endif /* HAVE_PNG_H */
+ };
+ static png_lib_handle *lib;
+
+#if HAVE_PNG_H
+ friend void set_error_handler (png_structp, png_infop);
+
+ png_structp _png;
+ png_infop _info;
+
+ static png_const_charp version_string;
+#endif
+ };
+
+
+// implementation
+
+// This function needs to be expanded verbatim at the location it's
+// invoked for the setjmp call to work as intended.
+#if HAVE_PNG_H
+#define set_error_handler(png, info) \
+ { \
+ if (!png || !info || setjmp (png_jmpbuf (png))) \
+ { \
+ pngstream::lib->destroy_write_struct (&png, &info); \
+ png = NULL; \
+ info = NULL; \
+ throw std::ios_base::failure ("write error"); \
+ } \
+ }
+#endif
+
+} // namespace iscan
+
+#endif /* !defined (iscan_pngstream_hh_included) */
diff --git a/lib/pnmstream.cc b/lib/pnmstream.cc
new file mode 100644
index 0000000..0f4ee6b
--- /dev/null
+++ b/lib/pnmstream.cc
@@ -0,0 +1,112 @@
+// pnmstream.cc -- image streams producing PNM files
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "pnmstream.hh"
+
+#include <ios>
+
+namespace iscan
+{
+ pnmstream::pnmstream (FILE *fp, const string& name)
+ : _stream (fp), _header (false)
+ {
+ if (!_stream) throw std::invalid_argument ("invalid file handle");
+ }
+
+ pnmstream::~pnmstream (void)
+ {
+ fflush (_stream);
+ }
+
+ basic_imgstream&
+ pnmstream::write (const byte_type *line, size_type n)
+ {
+ if (!line || 0 == n) return *this;
+ if (!_header)
+ {
+ write_header ();
+ }
+ size_t rv = fwrite (line, sizeof (byte_type), n, _stream);
+ if (n != rv) throw std::ios_base::failure ("write error");
+ return *this;
+ }
+
+ bool
+ pnmstream::is_usable (void)
+ {
+ return true;
+ }
+
+ basic_imgstream&
+ pnmstream::write_header (void)
+ {
+ check_consistency ();
+
+ string magic;
+ if (mono == _cspc) magic = "P4";
+ if (grey == _cspc) magic = "P5";
+ if (RGB == _cspc) magic = "P6";
+
+ int rv;
+ rv = fprintf (_stream, "%s\n%zd %zd\n", magic.c_str (), _h_sz, _v_sz);
+ if (0 > rv) throw std::ios_base::failure ("write error");
+ if ("P4" != magic)
+ {
+ rv = fprintf (_stream, "%d\n", (1 << _bits) - 1);
+ if (0 > rv) throw std::ios_base::failure ("write error");
+ }
+ _header = true;
+ return *this;
+ }
+
+ void
+ pnmstream::check_consistency (void) const
+ {
+ if (!(mono == _cspc || grey == _cspc || RGB == _cspc))
+ {
+ throw std::logic_error ("unsupported colour space");
+ }
+ if (_bits > 16)
+ {
+ throw std::logic_error ("maximum bit depth exceeded");
+ }
+ if (grey == _cspc || RGB == _cspc)
+ {
+ if (8 != _bits && 16 != _bits)
+ {
+ throw std::invalid_argument ("bit depth/colour space mismatch");
+ }
+ }
+ }
+
+} // namespace iscan
diff --git a/lib/pnmstream.hh b/lib/pnmstream.hh
new file mode 100644
index 0000000..a2c6905
--- /dev/null
+++ b/lib/pnmstream.hh
@@ -0,0 +1,73 @@
+// pnmstream.hh -- image streams producing PNM files
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifndef iscan_pnmstream_hh_included
+#define iscan_pnmstream_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "basic-imgstream.hh"
+
+#include <cstdio>
+#include <string>
+
+namespace iscan
+{
+ using std::string;
+
+ class pnmstream : public basic_imgstream
+ {
+ public:
+ typedef basic_imgstream::byte_type byte_type;
+ typedef basic_imgstream::size_type size_type;
+
+ explicit pnmstream (FILE *fp, const string& name = string ());
+ virtual ~pnmstream (void);
+
+ virtual basic_imgstream& write (const byte_type *line, size_type n);
+
+ static bool is_usable (void);
+
+ private:
+ basic_imgstream& write_header (void);
+ void check_consistency (void) const;
+
+ FILE *_stream;
+ bool _header;
+ };
+
+} // namespace iscan
+
+#endif /* !defined (iscan_pnmstream_hh_included) */
diff --git a/lib/tests/Makefile.am b/lib/tests/Makefile.am
new file mode 100644
index 0000000..c51b8ef
--- /dev/null
+++ b/lib/tests/Makefile.am
@@ -0,0 +1,46 @@
+## Makefile.am -- an -*- automake -*- template for Makefile.in
+## Copyright (C) 2011 SEIKO EPSON CORPORATION
+##
+## License: GPLv2+
+## Authors: AVASYS CORPORATION
+##
+## This file is part of the "Image Scan!" build infra-structure.
+##
+## The "Image Scan!" build infra-structure is free software.
+## You can redistribute it and/or modify it under the terms of the GNU
+## General Public License as published by the Free Software Foundation;
+## either version 2 of the License or at your option any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You ought to have received a copy of the GNU General Public License
+## along with this package. If not, see <http://www.gnu.org/licenses/>.
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/lib
+
+TESTS = \
+ run-test-pcx.sh
+
+check_PROGRAMS = \
+ test-pcx
+
+test_pcx_LDADD = \
+ ../libimage-stream.la \
+ -lstdc++
+test_pcx_SOURCES = \
+ test-pcx.cc \
+ pnm.c \
+ pnm.h
+
+EXTRA_DIST = \
+ even-width.pbm \
+ even-width.pgm \
+ even-width.ppm \
+ odd-width.pbm \
+ odd-width.pgm \
+ odd-width.ppm \
+ run-test-pcx.sh
diff --git a/lib/tests/even-width.pbm b/lib/tests/even-width.pbm
new file mode 100644
index 0000000..65bf750
--- /dev/null
+++ b/lib/tests/even-width.pbm
Binary files differ
diff --git a/lib/tests/even-width.pgm b/lib/tests/even-width.pgm
new file mode 100644
index 0000000..3b4bf37
--- /dev/null
+++ b/lib/tests/even-width.pgm
Binary files differ
diff --git a/lib/tests/even-width.ppm b/lib/tests/even-width.ppm
new file mode 100644
index 0000000..0179c95
--- /dev/null
+++ b/lib/tests/even-width.ppm
Binary files differ
diff --git a/lib/tests/odd-width.pbm b/lib/tests/odd-width.pbm
new file mode 100644
index 0000000..76a669a
--- /dev/null
+++ b/lib/tests/odd-width.pbm
Binary files differ
diff --git a/lib/tests/odd-width.pgm b/lib/tests/odd-width.pgm
new file mode 100644
index 0000000..9c29d53
--- /dev/null
+++ b/lib/tests/odd-width.pgm
Binary files differ
diff --git a/lib/tests/odd-width.ppm b/lib/tests/odd-width.ppm
new file mode 100644
index 0000000..0e945b9
--- /dev/null
+++ b/lib/tests/odd-width.ppm
Binary files differ
diff --git a/lib/tests/pnm.c b/lib/tests/pnm.c
new file mode 100644
index 0000000..d9ba1b6
--- /dev/null
+++ b/lib/tests/pnm.c
@@ -0,0 +1,141 @@
+/* pnm.c -- utility functions for testing purposes
+ * Copyright (C) 2019 SEIKO EPSON Corporation
+ *
+ * License: EPSON END USER SOFTWARE LICENSE
+ * Author : SEIKO EPSON Corporation
+ *
+ * This file is part of Image Scan! for Linux.
+ * It is distributed under the terms of the EPSON END USER SOFTWARE LICENSE.
+ *
+ * You should have received a verbatim copy of the EPSON END USER SOFTWARE
+ * LICENSE along with the software.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "pnm.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+pnm *
+read_pnm (const char *file)
+{
+ pnm image, *rv;
+
+ char *line = NULL;
+ size_t sz = 0;
+
+ FILE *fp;
+
+ if (file)
+ fp = fopen (file, "r");
+ else
+ fp = stdin;
+
+ if (!fp) return NULL;
+
+ getline (&line, &sz, fp); /* file magic number */
+ if ('P' != line[0]) return NULL;
+
+ image.depth = 8;
+ /**/ if ('6' == line[1]) image.format = 1;
+ else if ('5' == line[1]) image.format = 0;
+ else if ('4' == line[1]) { image.format = 0; image.depth = 1; }
+ else return NULL;
+
+ getline (&line, &sz, fp);
+ while ('#' == line[0]) /* skip header comments */
+ getline (&line, &sz, fp);
+ sscanf (line, "%d%d", &image.pixels_per_line, &image.lines);
+ if (1 < image.depth)
+ {
+ getline (&line, &sz, fp); /* max color value */
+ sscanf (line, "%d", &image.depth);
+ /**/ if (image.depth < (1 << 1)) image.depth = 1;
+ else if (image.depth < (1 << 8)) image.depth = 8;
+ else if (image.depth < (1 << 16)) image.depth = 16;
+ else return NULL;
+ }
+
+ free (line);
+ sz = 0;
+
+ image.bytes_per_line = (image.pixels_per_line
+ * (image.format ? 3 :1)
+ * (image.depth == 16 ? 2 : 1));
+ if (1 == image.depth)
+ {
+ image.bytes_per_line = (image.bytes_per_line + 7) / 8;
+ }
+ image.size = image.bytes_per_line * image.lines;
+
+ image.buffer = malloc (image.size);
+ if (!image.buffer) return NULL;
+ while (sz < image.size)
+ {
+ size_t s = fread (image.buffer, 1, image.size, fp);
+ if (0 == s)
+ {
+ if (ferror (fp))
+ {
+ fprintf (stderr, "error reading image file\n");
+ free (image.buffer);
+ return NULL;
+ }
+ if (feof (fp))
+ {
+ fprintf (stderr, "premature end of image file\n");
+ free (image.buffer);
+ return NULL;
+ }
+ }
+ sz += s;
+ }
+ fclose (fp);
+
+ rv = (pnm *) malloc (sizeof (pnm));
+ if (!rv)
+ {
+ free (image.buffer);
+ return NULL;
+ }
+ memcpy (rv, &image, sizeof (pnm));
+
+ return rv;
+}
+
+void
+write_pnm (const char *file, const pnm *image, const char *comment)
+{
+ FILE *fp;
+
+ if (file)
+ fp = fopen (file, "w");
+ else
+ fp = stdout;
+
+ if (!fp) return;
+
+ if (1 == image->depth)
+ {
+ fprintf (fp, "P4\n");
+ }
+ else
+ {
+ fprintf (fp, "P%d\n", (image->format ? 6 : 5));
+ }
+ fprintf (fp, "# %s\n", comment);
+ fprintf (fp, "%d %d\n", image->pixels_per_line, image->lines);
+ if (1 != image->depth)
+ {
+ fprintf (fp, "%d\n", (1 << image->depth) - 1);
+ }
+ fwrite (image->buffer, 1, image->bytes_per_line * image->lines, fp);
+ fflush (fp);
+ fclose (fp);
+}
diff --git a/lib/tests/pnm.h b/lib/tests/pnm.h
new file mode 100644
index 0000000..fcc2b9e
--- /dev/null
+++ b/lib/tests/pnm.h
@@ -0,0 +1,62 @@
+/* pnm.c -- utility functions for testing purposes
+ * Copyright (C) 2019 SEIKO EPSON Corporation
+ *
+ * License: EPSON END USER SOFTWARE LICENSE
+ * Author : SEIKO EPSON Corporation
+ *
+ * This file is part of Image Scan! for Linux.
+ * It is distributed under the terms of the EPSON END USER SOFTWARE LICENSE.
+ *
+ * You should have received a verbatim copy of the EPSON END USER SOFTWARE
+ * LICENSE along with the software.
+ */
+
+
+#ifndef pnm_h
+#define pnm_h
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ typedef struct {
+ void *buffer; /*!< image data */
+ size_t size; /*!< number of bytes allocated for buffer */
+ int format; /*!< zero for grayscale, non-zero for RGB */
+ int32_t bytes_per_line;
+ int32_t pixels_per_line;
+ int32_t lines;
+ int32_t depth;
+ } pnm;
+
+ /*! \brief Reads a PNM image from \a file
+ *
+ * Comments in the image file are allowed only between the first
+ * and second non-comment lines. That is, only a single block of
+ * consecutive comment lines after the first line is supported.
+ *
+ * Memory to hold the image data is acquired using malloc() and the
+ * caller is responsible for releasing this resource.
+ *
+ * Passing the \c NULL pointer for \a file will read from \c stdin.
+ */
+ pnm * read_pnm (const char *file);
+
+ /*! \brief Outputs a PNM \a image to \a file
+ *
+ * Files produced are reusable for input.
+ *
+ * Passing the \c NULL pointer for \a file will result in output on
+ * \c stdout.
+ */
+ void write_pnm (const char *file, const pnm *image,
+ const char *comment);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* defined (pnm_h) */
diff --git a/lib/tests/run-test-pcx.sh b/lib/tests/run-test-pcx.sh
new file mode 100755
index 0000000..c186ea2
--- /dev/null
+++ b/lib/tests/run-test-pcx.sh
@@ -0,0 +1,76 @@
+#! /bin/sh
+# run-test-pcx.sh -- unit test for pcxstream class
+# Copyright (C) 2011 SEIKO EPSON CORPORATION
+#
+# License: GPLv2+
+# Authors: AVASYS CORPORATION
+#
+# This file is part of the "Image Scan!" test suite.
+#
+# The "Image Scan!" test suite is free software.
+# You can redistribute it and/or modify it under the terms of the GNU
+# General Public License as published by the Free Software Foundation;
+# either version 2 of the License or at your option any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You ought to have received a copy of the GNU General Public License
+# along with this package. If not, see <http://www.gnu.org/licenses/>.
+
+if ! test -x ./test-pcx; then
+ echo "FAIL: ./test-pcx not found, run make first"
+ exit 1
+fi
+
+DO_COMPARE=`type convert >/dev/null && echo yes`
+if test "xyes" != x$DO_COMPARE; then
+ echo "INFO: compare test is skipped"
+fi
+
+# Compare test is additional.
+compare () {
+ FMT=`echo $SRC | sed 's,.*\.,,'`
+ convert "pcx:$DST" "$FMT:-" 2>/dev/null | cmp "$SRC" -
+ return $?
+}
+
+# Make temporary output in $builddir unless overridden. Only clean up
+# if tests succeed.
+run_test () {
+ SRC="$input"
+ DST=`mktemp ${TMPDIR:=.}/pcx.XXXXXXXX`
+
+ if ! ./test-pcx "$SRC" "$DST"; then
+ echo "FAIL: ./test-pcx $SRC $DST"
+ TEST_RESULT=FAIL
+ return
+ fi
+ if test "xyes" = x$DO_COMPARE; then
+ if ! compare "$SRC" "$DST"; then
+ echo "FAIL: compare $SRC $DST"
+ TEST_RESULT=FAIL
+ return
+ fi
+ fi
+ rm -f "$DST"
+}
+
+# `make check` normally sets $srcdir
+SRCDIR=${srcdir:=.}
+TEST_RESULT=PASS
+for input in \
+ "$SRCDIR/even-width.pbm" \
+ "$SRCDIR/even-width.pgm" \
+ "$SRCDIR/even-width.ppm" \
+ "$SRCDIR/odd-width.pbm" \
+ "$SRCDIR/odd-width.pgm" \
+ "$SRCDIR/odd-width.ppm" \
+ ; do
+ run_test
+done
+
+test "PASS" = "$TEST_RESULT"
+exit $?
diff --git a/lib/tests/test-pcx.cc b/lib/tests/test-pcx.cc
new file mode 100644
index 0000000..86e9a0e
--- /dev/null
+++ b/lib/tests/test-pcx.cc
@@ -0,0 +1,85 @@
+/* test-pcx.cc -- utility functions for testing purposes
+ * Copyright (C) 2019 SEIKO EPSON Corporation
+ *
+ * License: EPSON END USER SOFTWARE LICENSE
+ * Author : SEIKO EPSON Corporation
+ *
+ * This file is part of Image Scan! for Linux.
+ * It is distributed under the terms of the EPSON END USER SOFTWARE LICENSE.
+ *
+ * You should have received a verbatim copy of the EPSON END USER SOFTWARE
+ * LICENSE along with the software.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <iostream>
+#include "file-opener.hh"
+#include "imgstream.hh"
+#include "pnm.h"
+
+int main (int argc, char *argv[])
+{
+ using iscan::file_opener;
+ using iscan::imgstream;
+
+ char *i = NULL;
+ char *o = NULL;
+
+ pnm *img = NULL;
+ int x_res = 0;
+ int y_res = 0;
+
+ if (argc != 3)
+ {
+ std::cerr << "usage: ./test-pcx input.pnm output.pcx"
+ << std::endl;
+ return EXIT_FAILURE;
+ }
+ i = argv[1];
+ o = argv[2];
+
+ img = read_pnm (i);
+ if (!img)
+ return EXIT_FAILURE;
+
+ x_res = 300;
+ y_res = 300;
+
+ file_opener *fo = NULL;
+ imgstream *is = NULL;
+ iscan::file_format format = iscan::PCX;
+ fo = new file_opener (std::string (o));
+ is = create_imgstream (*fo, format, false);
+
+ is->next ();
+ is->size (img->pixels_per_line, img->lines);
+ is->depth (img->depth);
+ iscan::colour_space space;
+ if (1 == img->format)
+ space = iscan::RGB;
+ else if (0 == img->format && 1 == img->depth)
+ space = iscan::mono;
+ else if (0 == img->format)
+ space = iscan::grey;
+ else
+ return EXIT_FAILURE;
+ is->colour (space);
+ is->resolution (x_res, y_res);
+
+ int l;
+ char *ptr = (char *)img->buffer;
+ for (l=0; l<img->lines; ++l)
+ {
+ is->write (ptr, img->bytes_per_line);
+ ptr += img->bytes_per_line;
+ }
+ is->flush ();
+
+ delete is;
+ delete fo;
+
+ return 0;
+}
diff --git a/lib/tiffstream.cc b/lib/tiffstream.cc
new file mode 100644
index 0000000..d9bebf4
--- /dev/null
+++ b/lib/tiffstream.cc
@@ -0,0 +1,268 @@
+// tiffstream.cc -- image streams producing TIFF files
+// Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "tiffstream.hh"
+
+#include <cstdlib>
+#include <ios>
+#include <stdexcept>
+
+namespace iscan
+{
+ // Forward declaration of handlers and support functions.
+ static void handle_error (const char *module, const char *fmt, va_list ap);
+ static void handle_warning (const char *module, const char *fmt, va_list ap);
+
+
+ tiffstream::tiffstream (FILE *fp, const string& name)
+ : _stream (fp)
+ {
+ if (!_stream) throw std::invalid_argument ("invalid file handle");
+ init (name); // handles HAVE_TIFFIO_H only stuff
+ }
+
+ tiffstream::~tiffstream (void)
+ {
+#if HAVE_TIFFIO_H
+ lib->Close (_tiff);
+#endif
+ fflush (_stream);
+ }
+
+ imgstream&
+ tiffstream::write (const byte_type *line, size_type n)
+ {
+ if (!line || 0 == n) return *this;
+
+ if (0 == _page)
+ {
+ set_tags ();
+ }
+
+#if HAVE_TIFFIO_H
+ if (1 != lib->WriteScanline (_tiff, const_cast<char *> (line), _row, 1))
+ {
+ throw std::ios_base::failure ("failure writing TIFF scanline");
+ }
+ ++_row;
+#endif /* HAVE_TIFFIO_H */
+ return *this;
+ }
+
+ void
+ tiffstream::next (void)
+ {
+#if HAVE_TIFFIO_H
+ if (0 == _row) return;
+#endif
+
+ if (0 < _page)
+ {
+#if HAVE_TIFFIO_H
+ if (1 != lib->WriteDirectory (_tiff))
+ {
+ throw std::runtime_error ("failure writing TIFF directory");
+ }
+#endif
+ }
+ set_tags ();
+ }
+
+ bool
+ tiffstream::is_usable (void)
+ {
+ if (lib)
+ {
+ return lib->is_usable;
+ }
+
+ lib = new (std::nothrow) tiff_lib_handle ();
+ if (!lib)
+ {
+ return false;
+ }
+
+ lib->is_usable = false;
+ lib->message = string ();
+ lib->lib = NULL;
+#if HAVE_TIFFIO_H
+ try
+ {
+ imgstream::dlopen ("libtiff", validate);
+ }
+ catch (std::runtime_error& e)
+ {
+ lib->message = e.what ();
+ return lib->is_usable;
+ }
+#endif /* HAVE_TIFFIO_H */
+
+ return lib->is_usable;
+ }
+
+#if HAVE_TIFFIO_H
+#define funcsym(name) \
+ lib->name = ((tiffstream::tiff_lib_handle::name##_f) \
+ imgstream::dlsym (lib->lib, "TIFF"#name));
+#endif
+
+ bool
+ tiffstream::validate (lt_dlhandle h)
+ {
+ if (!h) return false;
+
+#if HAVE_TIFFIO_H
+ lib->lib = h;
+
+ funcsym (Open);
+ funcsym (FdOpen);
+ funcsym (Close);
+ funcsym (WriteDirectory);
+ funcsym (WriteScanline);
+ funcsym (Flush);
+ funcsym (SetField);
+ funcsym (SetErrorHandler);
+ funcsym (SetWarningHandler);
+
+ lib->is_usable = (lib->Open
+ && lib->FdOpen
+ && lib->Close
+ && lib->WriteDirectory
+ && lib->WriteScanline
+ && lib->Flush
+ && lib->SetField
+ && lib->SetErrorHandler
+ && lib->SetWarningHandler);
+
+ if (lib->is_usable)
+ {
+ lib->SetErrorHandler (handle_error);
+ lib->SetWarningHandler (handle_warning);
+ }
+#endif /* HAVE_TIFFIO_H */
+
+ return lib->is_usable;
+ }
+
+#if HAVE_TIFFIO_H
+#undef funcsym
+#endif
+
+ void
+ tiffstream::set_tags (void)
+ {
+ check_consistency ();
+
+#if HAVE_TIFFIO_H
+ lib->SetField (_tiff, TIFFTAG_SAMPLESPERPIXEL, (RGB == _cspc ? 3 : 1));
+
+ uint16 pm;
+ if (mono == _cspc) pm = PHOTOMETRIC_MINISWHITE;
+ if (grey == _cspc) pm = PHOTOMETRIC_MINISBLACK;
+ if (RGB == _cspc) pm = PHOTOMETRIC_RGB;
+ lib->SetField (_tiff, TIFFTAG_PHOTOMETRIC, pm);
+
+ if (RGB == _cspc)
+ lib->SetField (_tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
+
+ lib->SetField (_tiff, TIFFTAG_BITSPERSAMPLE, _bits);
+
+ lib->SetField (_tiff, TIFFTAG_IMAGEWIDTH , _h_sz);
+ lib->SetField (_tiff, TIFFTAG_IMAGELENGTH, _v_sz);
+ lib->SetField (_tiff, TIFFTAG_ROWSPERSTRIP, 1);
+
+ if (0 != _hres && 0 != _vres)
+ {
+ lib->SetField (_tiff, TIFFTAG_XRESOLUTION, float (_hres));
+ lib->SetField (_tiff, TIFFTAG_YRESOLUTION, float (_vres));
+ lib->SetField (_tiff, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH);
+ }
+
+ lib->SetField (_tiff, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
+
+ _row = 0;
+#endif /* HAVE_TIFFIO_H */
+ ++_page;
+
+ return;
+ }
+
+ void
+ tiffstream::check_consistency (void) const
+ {
+ if (!(mono == _cspc || grey == _cspc || RGB == _cspc))
+ {
+ throw std::logic_error ("unsupported colour space");
+ }
+ if (!(1 == _bits || 8 == _bits))
+ {
+ throw std::logic_error ("unsupported bit depth");
+ }
+ }
+
+ void
+ tiffstream::init (const string& name)
+ {
+ if (!is_usable ())
+ {
+ throw std::runtime_error (lib->message);
+ }
+
+#if HAVE_TIFFIO_H
+ _row = 0;
+ // libtiff uses 'b' to signal big-endian, not binary as fopen()!
+ _tiff = lib->Open (name.c_str (), "w");
+ if (!_tiff) throw std::bad_alloc ();
+#endif
+ }
+
+ tiffstream::tiff_lib_handle *tiffstream::lib = NULL;
+
+
+ // Definition of handlers and support functions.
+
+ /*! \todo Implement when debugging framework has been worked out
+ */
+ static void
+ handle_error (const char *module, const char *fmt, va_list ap)
+ {
+ }
+
+ /*! \todo Implement when debugging framework has been worked out
+ */
+ static void
+ handle_warning (const char *module, const char *fmt, va_list ap)
+ {
+ }
+
+} // namespace iscan
diff --git a/lib/tiffstream.hh b/lib/tiffstream.hh
new file mode 100644
index 0000000..657ca73
--- /dev/null
+++ b/lib/tiffstream.hh
@@ -0,0 +1,105 @@
+// tiffstream.hh -- image streams producing TIFF files
+// Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish software.
+// You can redistribute it and/or modify it under the terms of the GNU
+// General Public License as published by the Free Software Foundation;
+// either version 2 of the License or at your option any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+
+#ifndef iscan_tiffstream_hh_included
+#define iscan_tiffstream_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "imgstream.hh"
+
+#if HAVE_TIFFIO_H
+#include <tiffio.h>
+#include <cstdarg> // for error and warning handlers
+#endif
+
+namespace iscan
+{
+ using std::string;
+
+ class tiffstream : public imgstream
+ {
+ public:
+ typedef basic_imgstream::byte_type byte_type;
+ typedef basic_imgstream::size_type size_type;
+
+ tiffstream (FILE *fp, const string& name);
+ virtual ~tiffstream (void);
+
+ virtual imgstream& write (const byte_type *line, size_type n);
+
+ virtual void next (void);
+
+ static bool is_usable (void);
+
+ private:
+ void set_tags (void);
+ void check_consistency (void) const;
+
+ void init (const string& name);
+
+ FILE *_stream;
+
+ static bool validate (lt_dlhandle h);
+ struct tiff_lib_handle
+ {
+ bool is_usable;
+ string message;
+ lt_dlhandle lib;
+
+#if HAVE_TIFFIO_H
+ fundecl (TIFF *, Open, const char *, const char *);
+ fundecl (TIFF *, FdOpen, const int, const char *, const char *);
+ fundecl (void, Close, TIFF *);
+ fundecl (int, WriteDirectory, TIFF *);
+ fundecl (int, WriteScanline, TIFF *, tdata_t, uint32, tsample_t);
+ fundecl (int, Flush, TIFF *);
+ fundecl (int, SetField, TIFF *, ttag_t, ...);
+ fundecl (TIFFErrorHandler, SetErrorHandler, TIFFErrorHandler);
+ fundecl (TIFFErrorHandler, SetWarningHandler, TIFFErrorHandler);
+#endif /* HAVE_TIFFIO_H */
+ };
+
+ static tiff_lib_handle *lib;
+
+#if HAVE_TIFFIO_H
+ TIFF *_tiff;
+ uint32 _row;
+#endif
+ };
+
+} // namespace iscan
+
+#endif // iscan_tiffstream_hh_included
diff --git a/non-free/COPYING.EPSON.en.txt b/non-free/COPYING.EPSON.en.txt
new file mode 100644
index 0000000..0775979
--- /dev/null
+++ b/non-free/COPYING.EPSON.en.txt
@@ -0,0 +1,93 @@
+Please read out COPYING.EPSON and Privacy Statement(http://download.ebz.epson.net/ps/linux/).
+
+EPSON END USER SOFTWARE LICENSE AGREEMENT
+
+NOTICE TO USER: PLEASE READ THIS AGREEMENT CAREFULLY BEFORE INSTALLING OR USING THIS PRODUCT. IF YOU ARE LOCATED IN THE UNITED STATES, SECTIONS 19-23 OF THIS DOCUMENT APPLY TO YOU. SECTION 22 CONTAINS A BINDING ARBITRATION PROVISION THAT LIMITS YOUR ABILITY TO SEEK RELIEF IN A COURT BEFORE A JUDGE OR JURY, AND WAIVES YOUR RIGHT TO PARTICIPATE IN CLASS ACTIONS OR CLASS ARBITRATIONS FOR CERTAIN DISPUTES. AN “OPT-OUT” IS AVAILABLE UNDER SECTION 22.7 FOR THOSE WHO WISH TO BE EXCLUDED FROM THE ARBITRATION AND CLASS WAIVER.
+
+This is a legal agreement (“Agreement”) between you (an individual or entity, referred to hereinafter as “you”) and Seiko Epson Corporation (including its affiliates, “Epson”) for the enclosed software programs, including any related documentation, firmware, or updates (collectively referred to hereinafter as the “Software”). The Software is provided by Epson and its suppliers for use only with the corresponding Epson brand computer peripheral product (the “Epson Hardware”). BEFORE INSTALLING, COPYING OR OTHERWISE USING THE SOFTWARE, YOU NEED TO REVIEW AND AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT INCLUDING THE EPSON PRIVACY POLICY stated in Section 17. If you agree, click on the Agree (“ACCEPT”, “OK” or any similar representation of agreement) button below. If you do not agree with the terms and conditions of this Agreement, click on the Disagree (“EXIT”, “Cancel” or any similar representation of disagreement) button and return the Software, along with the packaging and related materials, to Epson or the place of purchase for a full refund.
+
+1. Grant of License. Epson grants you a limited, nonexclusive license to (i) download, install and use the Software for your personal and internal business use on hard disks or other computer storage devices, or in the case of a software application (also referred to as “Software”), on the smartphone, tablet, or other mobile device (collectively, “Device”), provided that the Software is used (i) only in a single location (e.g., a home or office or place of business), or in the case of a mobile device, on a Device owned or otherwise controlled by you, and (ii) only in connection with Epson Hardware owned by you. You may allow other users of the Epson Hardware connected to your network to use the Software, provided that you shall ensure that such users use the Software only in accordance with this Agreement. You agree to be responsible for and indemnify Epson for liabilities incurred as a consequence of use by such users. You may make backup copies of the Software, as necessary, provided the backup is only used to support your use of the Epson Hardware.
+
+2. Upgrades and Updates. If you acquire an upgrade, updated version, modified version, or additions to or for the Software from Epson, the upgrade, updated version, modified version, or addition, shall be included in the defined term Software and governed by this Agreement. You acknowledge that Epson has no obligation to provide you with any Updates (as defined below in this Section 2) to the Software. Epson may, however, from time to time, issue updated versions of the Software and the Software may automatically connect to Epson or third-party servers via the Internet to check for available updates to the Software, such as bug fixes, patches, upgrades, additional or enhanced functions, plug-ins and new versions (collectively, “Updates”) and may either (a) automatically electronically update the version of the Software that you are using on your personal device or (b) give you the option of manually downloading applicable Updates. If you installed the EPSON Software Updater and do not wish to allow Epson to check for available updates to the Software, you may disable this feature by uninstalling EPSON Software Updater. By installing the Software and not disabling any automated check for Updates, if applicable, you hereby agree and consent to automatically request and receive Updates from Epson or third-party servers, and that the terms and conditions of this Agreement shall apply to all of these Updates.
+
+3. Other Rights and Limitations. You agree not to modify, adapt or translate the Software and further agree not to attempt to reverse engineer, decompile, disassemble or otherwise attempt to discover the source code of the Software. You may not rent, lease, distribute, lend the Software to third parties or incorporate the Software into a revenue generating product or service. You may, however, transfer all of your rights to use the Software to another person or legal entity, provided that the recipient also agrees to the terms of this Agreement and you transfer the Software, including all copies, updates and prior versions, and the Epson Hardware, to such person or entity. The Software is licensed as a single unit, and its component programs may not be separated for some other use. Further, you agree not to place the Software onto or into a shared environment accessible via a public network such as the Internet or otherwise accessible by others outside the single location referred to in Section 1 above.
+
+4. Ownership. Title, ownership rights, and intellectual property rights in and to the Software shall remain with Epson or its licensors and suppliers. The Software is protected by United States Copyright Law, copyright laws of Japan and international copyright treaties, as well as other intellectual property laws and treaties. There is no transfer to you of any title to or ownership of the Software and this License shall not be construed as a sale of any rights in the Software. You agree not to remove or alter any copyright, trademark, registered mark and other proprietary notices on any copies of the Software. Epson and/or its licensors and suppliers reserve all rights not granted. The Software may also contain images, illustrations, designs and photos (“Materials”), and the copyright of such material belongs to Epson and/or its licensors and suppliers, protected by national and/or international intellectual property laws, conventions and treaties. For clarity, (1) the Materials shall be used for non-commercial purposes only, (2) the Materials shall be edited, adjusted and copied only in the manner designated by the Software, and (3) you may use the Materials only for lawful personal use, home use or as otherwise legally permitted.
+
+5. Open Source and Other Third-Party Components. Notwithstanding the foregoing license grant, you acknowledge that certain components of the Software may be covered by third-party licenses, including so-called “open source” software licenses, which means any software licenses approved as open source licenses by the Open Source Initiative or any substantially similar licenses, including without limitation any license that, as a condition of distribution of the software licensed under such license, requires that the distributor make the software available in source code format (such third-party components, “Third-Party Components”). A list of Third-Party Components, and associated license terms (as required), for particular versions of the Software is indicated at the end of this Agreement, relevant user manual/CD, or the license information displayed on your Device/in Software. To the extent required by the licenses covering Third-Party Components, the terms of such licenses will apply in lieu of the terms of this Agreement. To the extent the terms of the licenses applicable to Third-Party Components prohibit any of the restrictions in this Agreement with respect to such Third-Party Components, such restrictions will not apply to such Third-Party Component.
+
+6. Multiple Versions of Software. You may receive or obtain the Software in more than one version (e.g. for different operating environments; two or more language translation versions; downloaded from an Epson server or on a CD-ROM), however, regardless of the type or number of copies you receive, you still may use only the media or version appropriate for the license granted in Section 1 above.
+
+7. Disclaimer of Warranty and Remedy. If you obtained the Software by media from Epson or a dealer, Epson warrants that the media on which the Software is recorded will be free from defects in workmanship and materials under normal use for a period of 90 days from the date of delivery to you. If the media is returned to Epson or the dealer from which the media was obtained within 90 days of the date of delivery to you, and if Epson determines the media to be defective and provided the media was not subject to misuse, abuse, misapplication or use in defective equipment, Epson will replace the media, upon your return to Epson of the Software, including all copies of any portions thereof. You acknowledge and agree that the use of the Software is at your sole risk. THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY WARRANTY OF ANY KIND. EPSON AND ITS SUPPLIERS DO NOT AND CANNOT WARRANT THE PERFORMANCE OR RESULTS YOU MAY OBTAIN BY USING THE SOFTWARE. Epson does not warrant that the operation of the Software will be uninterrupted, error free, free from viruses or other harmful components or vulnerabilities, or that the functions of the Software will meet your needs or requirements. Epson’s sole and exclusive liability and your exclusive remedy for breach of warranty shall be limited to either, at Epson’s option, the replacement of the media for the Software or to refund your money upon returning the Software and Epson Hardware. Any replacement Software will be warranted for the remainder of the original warranty period or thirty (30) days, whichever is longer. If the above remedy fails for any reason, Epson’s entire liability for a breach of warranty shall be limited to a refund of the price paid for the Epson Hardware. Epson is not liable for performance delays or for nonperformance due to causes beyond its reasonable control. This Limited Warranty is void if failure of the Software resulted from accident, abuse, or misapplication. THE STATED LIMITED WARRANTIES AND REMEDY ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS. EPSON DISCLAIMS ALL OTHER WARRANTIES, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION, ALL WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. SOME STATES OR JURISDICTIONS, HOWEVER, DO NOT ALLOW EXCLUSIONS OR LIMITATIONS OF IMPLIED WARRANTIES, AND IN SUCH STATES, THE ABOVE LIMITATION MAY NOT APPLY TO YOU.
+
+8. Limitation of Liability. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL EPSON OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES, WHATSOEVER, WHETHER DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER ARISING UNDER CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY, BREACH OF WARRANTY, MISREPRESENTATION, OR OTHERWISE, INCLUDING WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS, ARISING OUT OF THE USE OF OR INABILITY TO USE THE SOFTWARE, OR ARISING OUT OF THIS AGREEMENT, EVEN IF EPSON OR ITS REPRESENTATIVE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF DAMAGES IN CERTAIN TRANSACTIONS, AND IN SUCH STATES, THE ABOVE LIMITATIONS AND EXCLUSIONS MAY NOT APPLY.
+
+9. U.S. Government Acquisition of the Software. This Section applies to all acquisitions of the Software by or for the U.S. Government (“Government”), or by any prime contractor or subcontractor (at any tier) under any contract, grant, cooperative agreement, “other transaction” (“OT”), or other activity with the Government. By accepting delivery of the Software, the Government, any prime contractor, and any subcontractor agree that the Software qualifies as “commercial” computer software within the meaning of FAR Part 12, paragraph (b) of FAR Subpart 27.405, or DFARS Subpart 227.7202, as applicable, and that no other regulation, or FAR or DFARS data rights clause, applies to the delivery of this Software to the Government. Accordingly, the terms and conditions of this Agreement govern the Government’s (and the prime contractor and subcontractor’s) use and disclosure of the Software, and supersede any conflicting terms and conditions of the contract, grant, cooperative agreement, OT, or other activity pursuant to which the Software is delivered to the Government. If this Software fails to meet the Government’s needs, if this Agreement is inconsistent in any respect with Federal law, or if the above cited FAR and DFARS provisions do not govern, the Government agrees to return the Software, unused, to Epson.
+
+10. Export Restriction. You agree that the Software will not be shipped, transferred or exported into any country or used in any manner prohibited by the United States Export Administration Act or any other export laws, restrictions or regulations.
+
+11. Entire Agreement. This Agreement is the entire agreement between the parties related to the Software and supersedes any purchase order, communication, advertisement, or representation concerning the Software.
+
+12. Binding Agreement; Assignees. This Agreement shall be binding upon, and inure to the benefit of, the parties hereto and their respective successors, assigns and legal representatives.
+
+13. Severability; Modifications. If any provision herein is found void or unenforceable by a court of competent jurisdiction (subject to Section 22.8 and 22.9 if you are a located in the U.S.), it will not affect the validity of the balance of the Agreement, which shall remain valid and enforceable according to its terms. This Agreement may only be modified in writing signed by an authorized representative of Epson.
+
+14. Indemnification. You agree that you will indemnify and hold harmless, and upon Epson’s request, defend Epson and its directors, officers, shareholders, employees and agents from and against any and all losses, liabilities, damages, costs, expenses (including reasonable attorneys’ fees), actions, suits, and claims arising from (i) any breach of any of your obligations in this Agreement or (ii) any use of the Software or the Epson Hardware. If Epson asks you to defend any such action, suit or claim, Epson will have the right, at its own expense, to participate in the defense thereof with counsel of its choice. You will not settle any third-party claims for which Epson is entitled to indemnification without the prior written approval of Epson.
+
+15. Termination. Without prejudice to any other rights Epson has, your license rights under Section 1 above and your warranty rights under Section 7 above, shall automatically terminate upon failure by you to comply with this Agreement. Upon termination of such rights, you agree that the Software, and all copies thereof, will be immediately destroyed.
+
+16. Capacity and Authority to Contract. You represent that you are of the legal age of majority in your state or jurisdiction of residence and have all necessary authority to enter into this Agreement, including, if applicable, due authorization by your employer to enter into this Agreement.
+
+17. Privacy, Information Processing. The Software may have the ability to connect over the Internet to transmit data to and from your Device. For example, if you install the Software, the Software may cause your Device to send information about your Epson Hardware such as model and serial number, country identifier, language code, operating system information, and Epson Hardware usage information to an Epson Internet site which may return promotional or service information to your Device for display. Any processing of information provided through the Software, shall be according to applicable data protection laws and the Epson Privacy Policy located at https://global.epson.com/privacy/area_select_confirm_eula.html. To the extent permitted by applicable laws, by agreeing to the terms of this Agreement and by installing the Software, you consent to the processing and storage of your information in and/or outside your country of residence. If there is a specific privacy policy incorporated into the Software and/or displayed when you use the Software (for example, in the case of certain software application software), such specific privacy policy shall prevail over the Epson Privacy Policy stated above.
+
+18. Third Party Websites. You may, through hypertext or other computer links from the Software, gain access to websites and use certain services that are not under the control of or operated by Epson, but rather are controlled by third parties. You acknowledge and agree that Epson is not responsible for such third party sites or services, including their accuracy, completeness, timeliness, validity, copyright compliance, legality, decency, quality, or any other aspect thereof. These third party websites/services are subject to different terms and conditions and when you access and use third party websites/services, you will be legally bound by the terms and conditions of those websites/services. If there is a conflict between this Agreement and terms and conditions of third party websites/services, the third party websites’/services’ terms and conditions will govern with respect to your access and use of those websites/services. Although Epson may provide a link to a third party website/service from the Software, such a link is not an authorization, endorsement, sponsorship or affiliation by Epson with respect to such website/services, its content, its owners or its providers. Epson provides such links for your reference and convenience only. Accordingly, Epson makes no representations whatsoever concerning such websites/services and does not provide any support related to such third party sites or services. Epson has not tested any information, products or software found on such websites/services and therefore cannot make any representations whatsoever with respect thereto. You agree that Epson is not responsible for the content or operation of such websites/services, and it is up to you to take precautions to ensure that whatever you select is free of items such as viruses, worms, Trojan horses and other items of a destructive nature. You are solely responsible for determining the extent to which you may use any content at any other websites/services to which you link from this Software.
+
+(IF YOU ARE LOCATED IN THE UNITED STATES, THE FOLLOWING SECTIONS 19 - 23 APPLY TO YOU)
+
+19. Ink Purchases. For certain Epson printer products sold in North America, the Software may also display an option to buy ink from Epson. If you click on the buy button, the Software will cause your Device to display Epson Hardware cartridge types and ink levels and provide other information about your cartridges, such as the colors, available cartridge sizes, and prices for replacement ink cartridges, which you may purchase online from Epson.
+
+20. Downloadable Updates. You may also be able to download from an Epson Internet site updates or upgrades to the Software if such updates or upgrades are made available. If you agree to install the Software, any transmissions to or from the Internet, and data collection and use, will be in accordance with Epson’s then-current Privacy Policy, and by installing the Software you agree that such then-current Privacy Policy shall govern such activities.
+
+21. Epson Accounts and Promotional Messages. In addition, if you install the Software and register your Epson Hardware with Epson, and/or you create an account at the Epson Store, and provided your consent to such use, you agree that Epson may merge the data collected in connection with installation of the Software, registration of your Epson Hardware and/or creation of your Epson Store account, consisting of personal information and non-personally identifiable information, and use such merged data to send you Epson promotional or service information. If you do not wish to send information about your Epson Hardware or receive promotional or service information, you will be able to disable these features on a Windows system through the Monitoring Preferences section in the driver. On a Mac operating system, you can disable these features by uninstalling the Epson Customer Research Participation and Low Ink Reminder software.
+
+22. DISPUTES, BINDING INDIVIDUAL ARBITRATION, AND WAIVER OF CLASS ACTIONS AND CLASS ARBITRATIONS
+
+22.1 Disputes. The terms of this Section 22 shall apply to all Disputes between you and Epson. The term “Dispute” is meant to have the broadest meaning permissible under law and includes any dispute, claim, controversy or action between you and Epson arising out of or relating to this Agreement, the Software, Epson Hardware, or other transaction involving you and Epson, whether in contract, warranty, misrepresentation, fraud, tort, intentional tort, statute, regulation, ordinance, or any other legal or equitable basis. “DISPUTE” DOES NOT INCLUDE IP CLAIMS, or more specifically, a claim or cause of action for (a) trademark infringement or dilution, (b) patent infringement, (c) copyright infringement or misuse, or (d) trade secret misappropriation (an “IP Claim”). You and Epson also agree, notwithstanding Section 22.6, that a court, not an arbitrator, may decide if a claim or cause of action is for an IP Claim.
+
+22.2 Binding Arbitration. You and Epson agree that all Disputes shall be resolved by binding arbitration according to this Agreement. ARBITRATION MEANS THAT YOU WAIVE YOUR RIGHT TO A JUDGE OR JURY IN A COURT PROCEEDING AND YOUR GROUNDS FOR APPEAL ARE LIMITED. Pursuant to this Agreement, binding arbitration shall be administered by JAMS, a nationally recognized arbitration authority, pursuant to its code of procedures then in effect for consumer related disputes, but excluding any rules that permit joinder or class actions in arbitration (for more detail on procedure, see Section 22.6 below). You and Epson understand and agree that (a) the Federal Arbitration Act (9 U.S.C. §1, et seq.) governs the interpretation and enforcement of this Section 22, (b) this Agreement memorializes a transaction in interstate commerce, and (c) this Section 22 shall survive termination of this Agreement.
+
+22.3 Pre-Arbitration Steps and Notice. Before submitting a claim for arbitration, you and Epson agree to try, for sixty (60) days, to resolve any Dispute informally. If Epson and you do not reach an agreement to resolve the Dispute within the sixty (60) days, you or Epson may commence an arbitration. Notice to Epson must be addressed to: Epson America, Inc., ATTN: Legal Department, 3840 Kilroy Airport Way, Long Beach, CA 90806 (the “Epson Address”). The Dispute Notice to you will be sent to the most recent address Epson has in its records for you. For this reason, it is important to notify us if your address changes by emailing us at EAILegal@ea.epson.com or writing us at the Epson Address above. Notice of the Dispute shall include the sender’s name, address and contact information, the facts giving rise to the Dispute, and the relief requested (the “Dispute Notice”). Following receipt of the Dispute Notice, Epson and you agree to act in good faith to resolve the Dispute before commencing arbitration.
+
+22.4 Small Claims Court. Notwithstanding the foregoing, you may bring an individual action in the small claims court of your state or municipality if the action is within that court’s jurisdiction and is pending only in that court.
+
+22.5 WAIVER OF CLASS ACTIONS AND CLASS ARBITRATIONS. YOU AND EPSON AGREE THAT EACH PARTY MAY BRING DISPUTES AGAINST THE OTHER PARTY ONLY IN AN INDIVIDUAL CAPACITY, AND NOT AS A PLAINTIFF OR CLASS MEMBER IN ANY CLASS OR REPRESENTATIVE PROCEEDING, INCLUDING WITHOUT LIMITATION FEDERAL OR STATE CLASS ACTIONS, OR CLASS ARBITRATIONS. CLASS ACTION LAWSUITS, CLASS-WIDE ARBITRATIONS, PRIVATE ATTORNEY-GENERAL ACTIONS, AND ANY OTHER PROCEEDING WHERE SOMEONE ACTS IN A REPRESENTATIVE CAPACITY ARE NOT ALLOWED. ACCORDINGLY, UNDER THE ARBITRATION PROCEDURES OUTLINED IN THIS SECTION, AN ARBITRATOR SHALL NOT COMBINE OR CONSOLIDATE MORE THAN ONE PARTY’S CLAIMS WITHOUT THE WRITTEN CONSENT OF ALL AFFECTED PARTIES TO AN ARBITRATION PROCEEDING.
+
+22.6 Arbitration Procedure. If you or Epson commences arbitration, the arbitration shall be governed by the rules of JAMS that are in effect when the arbitration is filed, excluding any rules that permit arbitration on a class or representative basis (the “JAMS Rules”), available at http://www.jamsadr.com or by calling 1-800-352-5267, and under the rules set forth in this Agreement. All Disputes shall be resolved by a single neutral arbitrator, and both parties shall have a reasonable opportunity to participate in the selection of the arbitrator. The arbitrator is bound by the terms of this Agreement. The arbitrator, and not any federal, state or local court or agency, shall have exclusive authority to resolve all disputes arising out of or relating to the interpretation, applicability, enforceability or formation of this Agreement, including any claim that all or any part of this Agreement is void or voidable. Notwithstanding this broad delegation of authority to the arbitrator, a court may determine the limited question of whether a claim or cause of action is for an IP Claim, which is excluded from the definition of “Disputes” in Section 22.1 above. The arbitrator shall be empowered to grant whatever relief would be available in a court under law or in equity. The arbitrator may award you the same damages as a court could, and may award declaratory or injunctive relief only in favor of the individual party seeking relief and only to the extent necessary to provide relief warranted by that party’s individual claim. In some instances, the costs of arbitration can exceed the costs of litigation and the right to discovery may be more limited in arbitration than in court. The arbitrator’s award is binding and may be entered as a judgment in any court of competent jurisdiction.
+
+You may choose to engage in arbitration hearings by telephone. Arbitration hearings not conducted by telephone shall take place in a location reasonably accessible from your primary residence, or in Orange County, California, at your option.
+
+a) Initiation of Arbitration Proceeding. If either you or Epson decides to arbitrate a Dispute, both parties agree to the following procedure:
+
+(i) Write a Demand for Arbitration. The demand must include a description of the Dispute and the amount of damages sought to be recovered. You can find a copy of a Demand for Arbitration at http://www.jamsadr.com (“Demand for Arbitration”).
+
+(ii) Send three copies of the Demand for Arbitration, plus the appropriate filing fee, to: JAMS, 500 North State College Blvd., Suite 600 Orange, CA 92868, U.S.A.
+
+(iii) Send one copy of the Demand for Arbitration to the other party (same address as the Dispute Notice), or as otherwise agreed by the parties.
+
+b) Hearing Format. During the arbitration, the amount of any settlement offer made shall not be disclosed to the arbitrator until after the arbitrator determines the amount, if any, to which you or Epson is entitled. The discovery or exchange of non-privileged information relevant to the Dispute may be allowed during the arbitration.
+
+c) Arbitration Fees. Epson shall pay, or (if applicable) reimburse you for, all JAMS filings and arbitrator fees for any arbitration commenced (by you or Epson) pursuant to provisions of this Agreement.
+
+d) Award in Your Favor. For Disputes in which you or Epson seeks $75,000 or less in damages exclusive of attorney’s fees and costs, if the arbitrator’s decision results in an award to you in an amount greater than Epson’s last written offer, if any, to settle the Dispute, Epson will: (i) pay you $1,000 or the amount of the award, whichever is greater; (ii) pay you twice the amount of your reasonable attorney’s fees, if any; and (iii) reimburse you for any expenses (including expert witness fees and costs) that your attorney reasonably accrues for investigating, preparing, and pursuing the Dispute in arbitration. Except as agreed upon by you and Epson in writing, the arbitrator shall determine the amount of fees, costs, and expenses to be paid by Epson pursuant to this Section 22.6d).
+
+e) Attorney’s Fees. Epson will not seek its attorney’s fees and expenses for any arbitration commenced involving a Dispute under this Agreement. Your right to attorney’s fees and expenses under Section 22.6(d) above does not limit your rights to attorney’s fees and expenses under applicable law; notwithstanding the foregoing, the arbitrator may not award duplicative awards of attorney’s fees and expenses.
+
+22.7 Opt-out. You may elect to opt-out (exclude yourself) from the final, binding, individual arbitration procedure and waiver of class and representative proceedings specified in this Agreement by sending a written letter to the Epson Address within thirty (30) days of your assent to this Agreement (including without limitation the purchase, download, installation of the Software or other applicable use of Epson Hardware, products and services) that specifies (i) your name, (ii) your mailing address, and (iii) your request to be excluded from the final, binding individual arbitration procedure and waiver of class and representative proceedings specified in this Section 22. In the event that you opt-out consistent with the procedure set forth above, all other terms shall continue to apply, including the requirement to provide notice prior to litigation.
+
+22.8 Amendments to Section 22. Notwithstanding any provision in this Agreement to the contrary, you and Epson agree that if Epson makes any future amendments to the dispute resolution procedure and class action waiver provisions (other than a change to Epson’s address) in this Agreement, Epson will obtain your affirmative assent to the applicable amendment. If you do not affirmatively assent to the applicable amendment, you are agreeing that you will arbitrate any Dispute between the parties in accordance with the language of this Section 22 (or resolve disputes as provided for in Section 22.7, if you timely elected to opt-out when you first assented to this Agreement).
+
+22.9 Severability. If any provision in this Section 22 is found to be unenforceable, that provision shall be severed with the remainder of this Agreement remaining in full force and effect. The foregoing shall not apply to the prohibition against class or representative actions as provided in Section 22.5. This means that if Section 22.5 is found to be unenforceable, the entire Section 22 (but only Section 22) shall be null and void.
+
+23. For New Jersey Residents. NOTWITHSTANDING ANY TERMS SET FORTH IN THIS AGREEMENT, IF ANY OF THE PROVISIONS SET FORTH IN SECTIONS 7 OR 8 ARE HELD UNENFORCEABLE, VOID OR INAPPLICABLE UNDER NEW JERSEY LAW, THEN ANY SUCH PROVISION SHALL NOT APPLY TO YOU BUT THE REST OF THE AGREEMENT SHALL REMAIN BINDING ON YOU AND EPSON. NOTWITHSTANDING ANY PROVISION IN THIS AGREEMENT, NOTHING IN THIS AGREEMENT IS INTENDED TO, NOR SHALL IT BE DEEMED OR CONSTRUED TO, LIMIT ANY RIGHTS AVAILABLE TO YOU UNDER THE TRUTH-IN-CONSUMER CONTRACT, WARRANTY AND NOTICE ACT.
+
+Rev. December 2018 \ No newline at end of file
diff --git a/non-free/COPYING.EPSON.ja.txt b/non-free/COPYING.EPSON.ja.txt
new file mode 100644
index 0000000..61b3da8
--- /dev/null
+++ b/non-free/COPYING.EPSON.ja.txt
@@ -0,0 +1,93 @@
+Please read out COPYING.EPSON and Privacy Statement(http://download.ebz.epson.net/ps_j/linux/).
+
+ソフトウェア使用許諾契約書
+
+お客様へ:この製品をインストールまたは使用する前にこのソフトウェア使用許諾契約書を慎重にお読みください。お客様の居住地がアメリカ合衆国内である場合、このソフトウェア使用許諾契約書の第19条から第23条がお客様に適用されます。第22条には、紛争の際に、お客様は、裁判官や陪審の前で裁判所において救済を求める権利が制限され、集団訴訟または集団仲裁に参加する権利を放棄する仲裁条項が含まれています。お客様がこのような仲裁ならびに集団訴訟および集団仲裁についての権利放棄を希望されない場合は、第22条第7項にしたがって「オプトアウト」手続を利用することができます。
+
+このソフトウェア使用許諾契約書(以下「この契約」といいます)は、お客様(お客様が個人または法人であるかを問わず、以下「お客様」といいます)が、この契約に同梱されるソフトウェアプログラムおよびその付属書類、ファームウェア、アップデート版(以下総称して「ソフトウェア」といいます)を使用するための、お客様とセイコーエプソン株式会社(その関係会社を含み、以下「当社」といいます)との法的拘束力のある契約書です。ソフトウェアは、当社または当社の指定販売業者から提供され、当社ブランドを付したコンピュータ周辺機器(以下「当社製品」といいます)においてのみ使用することができます。ソフトウェアをインストールし、コピーし、またはその他の方法で使用する前に、お客様はこの契約および第17条に定める当社のプライバシーポリシーを確認し、これに同意する必要があります。お客様が同意する場合、「同意する」(その他「承諾する」、「OK」など合意を表すもの)ボタンをクリックしてください。この契約に同意できない場合は、「同意しない」(その他「終了」、「キャンセル」など不合意を表すもの)ボタンをクリックし、ソフトウェアをパッケージおよび関連物品と共に当社または購入した店舗に返却し、払戻しを受けてください。
+
+1 使用許諾 当社は、お客様に対し、(i)お客様の個人および社内業務用途でソフトウェアをハードディスクその他のコンピュータ記憶装置に、またはアプリケーションソフトウェア(アプリケーションソフトウェアも「ソフトウェア」といいます)の場合にはスマートフォン、タブレットその他の携帯端末(以下総称して「お客様端末」といいます)にダウンロードし、インストールし、使用する限定的および非独占的な権利を許諾します。ただし、お客様はソフトウェアを、(i)一つの場所(お客様の自宅またはオフィス、事業所など)、または携帯端末の場合にはお客様が所有または管理するお客様端末においてのみ、かつ(ii)お客様が所有する当社製品と関連する場合のみ使用できます。お客様は、お客様のネットワークに接続される当社製品の他の使用者に対し、ソフトウェアの使用を許可することができます。ただし、お客様は、そのような他の使用者がこの契約のみに従ってソフトウェアを使用することを保証するものとします。お客様は、そのような他の使用者によるソフトウェアの使用によって発生する義務に関し責任を負うとともに、当社を補償し、免責することに同意するものとします。お客様は、お客様による当社製品の使用をサポートする目的に限り、必要に応じてソフトウェアのバックアップコピーを作成することができます。
+
+2 アップグレードおよびアップデート お客様が当社よりソフトウェアのアップグレード版、アップデート版、修正版または追加版を入手した場合、当該アップグレード版、アップデート版、修正版または追加版はこの契約におけるソフトウェアに含まれ、この契約が適用されるものとします。お客様は、当社がソフトウェアの更新版(本条にて定義されるものとします)を提供する義務を負わないことに合意するものとします。ただし、当社は、当社の裁量で、ソフトウェアのアップデート版を提供することがあります。ソフトウェアは、不具合修正版、アップグレード版、機能追加または機能向上、プラグイン、新しいバージョンなどのソフトウェアのアップデート版(以下「更新版」といいます)が利用できるかどうか確認するために、当社または第三者のサーバーにインターネットを介して自動的に接続することがあります。また、ソフトウェアは(i)お客様が自己の個人端末で使用しているソフトウェアのバージョンを自動更新し、または(ii) 適切な更新版を手動でダウンロードできる機能を提供することがあります。お客様がEPSON Software Updaterをインストールしており、当社による利用可能なソフトウェア更新版の確認を希望されない場合は、EPSON Software Updaterをアンインストールすることでこの機能を無効にすることができます。ソフトウェアをインストールし、更新版の自動チェックを無効にしない場合、お客様は、当社または第三者のサーバーからアップデートを自動的に要求し、受信すること、および全ての更新版にこの契約の条件が適用されることに同意したものとします。
+
+3 その他の権利および制限 お客様は、ソフトウェアを変更し、改変し、翻訳してはならず、また、リバースエンジニアリング、逆コンパイル、逆アセンブルその他の方法によりソフトウェアのソースコードの追跡を試みることはできません。お客様は、ソフトウェアを第三者にレンタルし、リースし、配布し、貸付することはできず、また、ソフトウェアを収益性のある製品またはサービスに組み込むことはできません。ただし、お客様は、ソフトウェアの受領者がこの契約の条項に同意する場合で、当該第三者または他の法人にソフトウェア(全ての複製物、アップデート版、旧版を含みます)および当社製品を譲渡するときは、ソフトウェアを使用するための全ての権利を第三者または他の法人に譲渡することができます。ソフトウェアは単一の構成物としてライセンスされており、お客様は、その構成物である各プログラムを他の用途のために分離することはできません。さらに、お客様は、インターネットなどの公衆ネットワークを介してアクセス可能な共有環境に、またはこの契約第1条に記載する単一の場所以外の他の人がアクセス可能な共有環境上にソフトウェアを置かないことに同意するものとします。
+
+4 権利の帰属 ソフトウェアの権原、権限および知的財産権は、当社または当社のライセンサーおよびサプライヤーに帰属します。ソフトウェアは、米国著作権法、日本国著作権法および著作権に関する国際条約ならびにその他の知的財産権に関する法令および条約によって保護されています。ソフトウェアの所有権その他いかなる権利もお客様に移転するものではなく、この契約はソフトウェアのいかなる権利の販売と解釈されるものではありません。お客様は、ソフトウェアおよびその複製物に記載されている著作権に関する表示、商標、登録商標およびその他の権利に関する表示を削除し、または変更しないことに同意するものとします。当社ならびにそのライセンサーおよびサプライヤーは、お客様に付与されていない全ての権利を留保します。ソフトウェアには画像、イラスト、デザインおよび写真(以下「当社資料」といいます)が含まれる可能性があります。当該当社資料の著作権は当社ならびに当社のライセンサーおよびサプライヤーに帰属し、国および国際的な知的財産権に関する法令、協定または条約によって保護されています。お客様は、当社資料を、(i) 非営利目的のみに使用し、(ii) ソフトウェアによって指定された方法に限り編集、調整または複製し、(iii) 適法な個人的使用、家庭内使用またはその他の法的に許可されている方法でのみ使用するものとします。
+
+5 オープンソースおよびその他第三者の構成物 この契約に基づくお客様へのソフトウェアの使用許諾にかかわらず、お客様は、ソフトウェアの一定の構成物(以下「第三者ソフトウェア」といいます)について、「オープンソース」ソフトウェアライセンスを含む第三者の使用許諾条件が適用される可能性があることに同意します。「オープンソース」ソフトウェアライセンスとは、Open Source Initiativeがオープンソースライセンスとして承認したソフトウェアの使用許諾、または実質的に類似した使用許諾条件で配布されるソフトウェアの使用許諾をいい、配布者がソフトウェアをソースコードが入手可能な状態で配布することを要求する使用許諾を含みます。特定のバージョンについての第三者ソフトウェアのリストおよび関連する使用許諾条件は、この契約の末尾、関連するユーザーマニュアルもしくはCD、またはお客様端末もしくはソフトウェアに表示されるライセンス情報に記載されています。第三者ソフトウェアに適用される使用許諾条件によって要求される範囲で、この契約条件に代わり、当該使用許諾条件が適用されます。第三者ソフトウェアに適用される使用許諾条件がこの契約による当該第三者ソフトウェアに関する制限を禁止している限り、当該制限は第三者ソフトウェアには適用されません。
+
+6 複数のソフトウェアのバージョン お客様は、ソフトウェアを複数のバージョン(異なる動作環境、2つ以上の言語翻訳バージョン、当社サーバーからのダウンロードまたはCD-ROM等)で受領し、または取得することができます。ただし、お客様がいかなるバージョンまたはいかなる数のソフトウェアを受領したときでも、お客様はこの契約の第1条において許諾された使用許諾条件に適合したメディアまたはバージョンのみを使用することができます。
+
+7 保証および救済の放棄 お客様が当社または代理店から記録メディアによってソフトウェアを入手した場合、当社は、ソフトウェアが記録された記録メディアについて、お客様に納入された日から90日間、通常の使用の下で製造上および材料上の欠陥がないことを保証します。お客様への納入から90日以内に当社または販売店に記録メディアが返品された場合で、当社が記録メディアに欠陥があると判断し、かつメディアが誤用、濫用、誤った適用、または欠陥のある機器で使用されていないときは、お客様による当社へのソフトウェア(その一部の全ての複製物を含む)の返却時に、当社は記録メディアを交換します。お客様は、自己の責任においてソフトウェアを使用することを認め、これに同意するものとします。ソフトウェアは「現状有姿」で提供されており、いかなる種類の保証もありません。当社および当社のサプライヤーは、ソフトウェアの性能およびその使用結果について一切の保証を行いません。当社は、ソフトウェアの動作に中断がなく、エラーがなく、ウイルスやその他の有害な構成物や脆弱性がないこと、またはソフトウェアの機能がお客様の要望や要件を満たしていることを保証しません。当社の唯一かつ排他的な責任および当社の保証違反についてのお客様の唯一の救済措置は、当社の選択により、ソフトウェアの記録メディアの交換またはソフトウェアおよび当社製品の返品時における購入金額の返金のいずれかに限定されます。交換されたソフトウェアは、交換前のソフトウェアの残存保証期間または30日間のいずれか長い期間内で保証されます。上記の救済が何らかの理由で奏功しない場合、当社の保証違反についての責任は、当社製品の購入代金の返金に限られます。当社は、自己の合理的な管理を超えた原因による履行遅滞や不履行について責任を負いません。この限定的な保証は、ソフトウェアの不具合が偶発的事故、濫用または誤用によるものであった場合、無効となります。本条に記載されている限定的な保証および救済は、排他的であり、その他の全ての保証の代わりになります。当社は、特定目的への適合性、商品性および第三者権利の非侵害を含め、明示または黙示を問わず、他の全ての保証を否認します。ただし、一部の州または法域では、黙示の保証の除外または制限を認めていないため、上記の制限が適用されないことがあります。
+
+8 責任の制限 適用される法律が許容する最大限の範囲において、当社または当社のサプライヤーは、契約、不法行為(過失を含む)、厳格責任、保証違反、不実表示その他原因の如何を問わず、また、直接損害、間接損害、特別損害、付随損害または派生損害であるかを問わず、ビジネス上の利益の損失、事業の中断、ビジネス情報の損失またはその他の金銭的な損害を含め、ソフトウェアの使用もしくは使用不能から生じ、またはこの契約から生じた一切の責任を負わないものとします。これは当社または当社のサプライヤーがそのような可能性を知らされていた場合にも同様です。一部の州や法域では一定の取引における損害賠償の除外や制限を認めていないため、そのような州または法域においては上記の制限が適用されないことがあります。
+
+9 米国政府によるソフトウェアの入手 この条項は、米国政府(「政府」)による、もしくは政府のためのソフトウェアのあらゆる入手、または政府との何らかの契約、政府補助、共同契約、「その他取引」(「OT」)もしくは他の活動の下での元請業者・下請業者(どのような階層でも)によるソフトウェアのあらゆる入手に適用されます。政府、元請業者および下請業者は、ソフトウェアの引渡しを受けることにより、ソフトウェアが適用されるFAR Part 12、FAR Subpart 27.405のparagraph (b)、またはDFARS Subpart 227.7202の意味における「商業上の」コンピューターソフトウェアに該当すること、およびその他の規則またはFAR・DFARSのデータ権利に関する条項が政府へのソフトウェアの引渡しには適用されないことに同意します。したがって、この契約の条項は、政府(および元請業者と下請業者)によるソフトウェアの使用と公開に適用され、また、それに従って政府にソフトウェアが引き渡された契約、政府補助、共同契約、OTまたは他の活動であってこの契約と矛盾する条項に優先します。もし、ソフトウェアが政府の要求を満たすことができなかったり、この契約が何らかの点で連邦法と矛盾したり、または上記に引用されているFARとDFARSの条項が適用されない場合には、政府は、ソフトウェアを未使用の状態で当社に返品することに同意します。
+
+10 輸出規制 お客様は、ソフトウェアを、米国輸出規制またはその他の輸出法令、輸出制限もしくは輸出規制によって禁止されている国へ輸送し、移送し、輸出し、または禁止されている方法で使用しないことに同意します。
+
+11 完全合意 この契約は、当事者間におけるソフトウェアに関する完全な合意であり、ソフトウェアに関するいかなる発注書、連絡、通知または表明に優先します。
+
+12 拘束力のある契約;承継人 この契約は、契約当事者、当事者の承継人、譲受人および法的代理人の利益のために効力を生じ、それらを拘束します。
+
+13 分離可能性;修正 この契約の一部の条項が管轄裁判所によって(アメリカ合衆国在住のお客様は第22条第8項および第22条第9項に従い)無効または法的強制力がないと判断された場合、それはこの契約の他の条項の有効性に影響を及ぼすものではなく、他の条項はその条件に従って有効かつ法的強制力を有するものとします。この契約は、当社を代表する権限を有する者の署名の入った書面によってのみ変更することができます。
+
+14 補償 お客様は、(i) この契約上のお客様のいかなる義務違反、または(ii) ソフトウェアもしくは当社製品の使用によって生じた、いかなる損失、責任、損害、費用、実費(合理的な弁護士費用を含みます)、訴訟、紛争および請求について、当社ならびにその取締役、役員、株主、従業員および代理人を補償し、免責し、当社の要求に応じ防御することに同意します。仮に当社がお客様にいかなるそのような訴訟または請求を防御することを要求する場合、当社は自己負担で当社が選択する弁護士によりその防御に参加する権利を有します。お客様は、当社の事前の書面による同意なく、当社が補償を受ける権利を有する第三者との紛争につき和解することはできません。
+
+15 契約終了 当社が有する他のいかなる権利を損なうことなく、前述の第1条に基づくお客様の使用権および第7条に基づく保証を受ける権利は、お客様がこの契約を遵守しなかった場合、自動的に終了します。そのような権利が終了した時点で、お客様はソフトウェアおよびその全ての複製物を直ちに削除することに同意するものとします。
+
+16 契約締結権限および能力 お客様は、お客様の居住する州または国の法令における成人年齢であり、該当する場合、お客様が自身の雇用主からこの契約を締結するための正当な権限を受けていることを含め、この契約を締結するために必要な権限を有していることを表明します。
+
+17 プライバシーおよび情報の処理 ソフトウェアには、インターネットを介してお客様端末との間でデータを送受信する機能があります。たとえば、ソフトウェアをインストールすると、お客様端末から、当社製品に関する機種、シリアル番号、国別コード、言語コード、オペレーティングシステム情報、当社製品の使用情報などの情報が当社インターネットサイトに送信され、当社インターネットサイトはお客様端末にプロモーション情報やサービスに関する情報を表示させることがあります。ソフトウェアを通じ提供される情報の処理は、データ保護に関する適用法令およびhttps://global.epson.com/privacy/area_select_confirm_eula.htmlに明記される当社のプライバシーポリシーに従って行われます。適用法令で許可されている範囲で、お客様がこの契約の条項に同意し、ソフトウェアをインストールすることにより、お客様は、お客様の情報がお客様の居住国内または国外で処理および保管されることに同意するものとします。特定のプライバシーポリシーがソフトウェアに組み込まれているか、ソフトウェアを使用する際に表示される場合は(例:アプリケーションソフトウェアなど)、当該特定のプライバシーポリシーが上記の当社プライバシーポリシーに優先するものとします。
+
+18 第三者のウェブサイト お客様は、ソフトウェアからのハイパーテキストまたはその他のコンピュータリンクを介して、当社が管理または運営していない、第三者によって管理されている一定のウェブサイトおよびサービスを使用することができます。お客様は、当社が当該第三者のウェブサイトおよびサービスの正確性、完全性、適時性、有効性、著作権法令遵守、適法性、良質性、品質その他一切の事項に関し責任を負わないことを認め、これに同意するものとします。当該第三者のウェブサイトおよびサービスには異なる利用規約が適用され、また、お客様が第三者のウェブサイトおよびサービスにアクセスし、使用すると、当該第三者のウェブサイトおよびサービスの利用規約に法的に拘束されます。この契約と第三者のウェブサイトおよびサービスの利用規約との間に矛盾がある場合、当該ウェブサイトおよびサービスへのアクセスおよび使用に関しては、当該第三者のウェブサイトおよびサービスの利用規約が適用されます。当社はソフトウェアから第三者のウェブサイトおよびサービスへのリンクを提供することがありますが、そのようなリンクは、当該ウェブサイトおよびサービスならびに当該ウェブサイトおよびサービスのコンテンツ、所有者または提供者に関する当社による許可、承認、スポンサーシップまたは提携ではありません。当社はお客様による閲覧と利便性のためにそのようなリンクを提供しています。したがって、当社は、当該ウェブサイトおよびサービスに関するいかなる表明もせず、当該第三者のサイトおよびサービスに関するいかなるサポートも提供しません。当社は、当該ウェブサイトおよびサービスに記載されている情報、製品またはソフトウェアを検査していないため、当該情報、製品またはソフトウェアに関するいかなる表明もすることができません。お客様は、当社が当該ウェブサイトおよびサービスの内容や運営に関し一切の責任を負わないことに同意します。お客様が選択したいかなるものにもウイルス、ワーム、トロイの木馬その他破壊的な性質を持つものが含まれていないことを保証する予防措置を取ることは、お客様自身に委ねられています。お客様は、ソフトウェアからリンクする他のウェブサイトおよびサービス上のコンテンツを使用する範囲を決めることについて、単独で責任を負うものとします。
+
+(お客様の居住地がアメリカ合衆国内である場合、以下の第19条から第23条がお客様に適用されます)
+
+19 インクの購入 北米で販売されているEpsonプリンタ製品については、ソフトウェアの機能によってEpsonからインクを購入するオプションが表示されることがあります。購入ボタンをクリックすると、お客様端末は、お客様が当社からオンラインで購入できる当社製品のカートリッジの種類とインクレベルを表示し、色、使用可能なカートリッジサイズ、交換用インクカートリッジの価格など、カートリッジに関するその他の情報を提供します。
+
+20 ダウンロード可能なアップデート版 お客様は、ソフトウェアのアップデート版またはアップグレード版が利用可能になった場合、Epsonのインターネットサイトから当該アップデート版またはアップグレード版をダウンロードすることができます。お客様がソフトウェアのインストールに同意した場合、インターネットへの、またはインターネットからの送受信、情報の収集および使用はその時点で最新の当社のプライバシーポリシーに従って行われ、お客様は、ソフトウェアをインストールすることにより、その時点で最新の当社のプライバシーポリシーがそのような情報の送受信、収集および使用に適用されることに同意したことになります。
+
+21 当社アカウントおよび広告メール お客様がソフトウェアをインストールし、当社製品を当社に登録し、かつ/またはEpson Storeでアカウントを作成し、その使用に同意した場合は、個人情報および個人を特定できない情報から構成される、インストールに関連して収集された情報、当社製品の登録情報およびEpson Storeアカウント作成情報を統合すること、ならびに当該統合した情報を用いて当社のプロモーション情報やサービス情報をお客様に送信することに同意したことになります。お客様が当社製品に関する情報を送信し、またはプロモーション情報やサービス情報を受信したくない場合、Windowsシステムではドライバーに含まれるMonitoring Preferencesを介してこれらの機能を無効にすることができます。Macオペレーティングシステムでは、Epson Customer Research ParticipationおよびLow Ink Reminderソフトウェアをアンインストールすることにより、これらの機能を無効にすることができます。
+
+22 紛争、拘束力のある個別仲裁、集団訴訟・集団仲裁の放棄
+
+22.1 紛争 この第22条の条項は、お客様と当社のあらゆる紛争に適用されます。「紛争」とは、法の下で可能な限り広い意味を持ち、契約、保証、不実表示、詐欺行為、不法行為、故意の不法行為、制定法、規則、条例によるかその他の法律上・衡平法上の理由に基づくかを問わず、この契約、ソフトウェア、当社製品、その他お客様と当社に関係する取引に起因し、またはこれに関係するお客様と当社の紛争、請求、論争、訴訟を含みます。「紛争」には、知的財産権に関する請求は含みません。より詳細にいえば、知的財産権に関する請求とは(a)商標権の侵害または希釈化、(b)特許権侵害、(c)著作権侵害・濫用、(d)営業秘密の不正使用に関する請求または請求の原因を意味します(以下「知的財産権に関する請求」といいます)。また、お客様と当社は、第22条第6項にかかわらず、仲裁人ではなく裁判所が、主張または訴訟原因が知的財産権に関する請求であるかを決定できることに同意します。
+
+22.2 拘束力のある仲裁 お客様と当社は、この契約に従い、全ての紛争が拘束力のある仲裁によって解決されることに同意します。仲裁は、お客様が裁判手続における裁判官または陪審による裁判を受ける権利を放棄し、お客様の上訴理由が制限されることを意味します。この契約に従い、拘束力のある仲裁は、全国的に認識されている仲裁機関であるJAMSによって、消費者関連の紛争に適用のあるその時において有効なJAMSの手続法に従って行われます。しかし、仲裁において併合または集団訴訟を可能にするルールは除外されます(手続きに関する詳細については、第22条第6項のご確認をお願いします)。お客様と当社は、(a)米国連邦仲裁法(9 U.S.C. §1, et seq.)が第22条の解釈と施行に適用されること、(b)この契約は州際通商における取引を記録するものであること、および(c)この第22条はこの契約終了後も存続することを理解し、これに同意します。
+
+22.3 仲裁前の手続および通知 お客様および当社は、仲裁の申立てを提出する前に、60日間、紛争の解決のために協議するものとします。当該協議が60日以内に整わなかった場合、お客様または当社は仲裁を開始することができます。当社に対する紛争に係る通知は、Epson America, Inc., ATTN: Legal Department, 3840 Kilroy Airport Way, Long Beach, CA 90806(以下「当社宛先」といいます)に送られなければなりません。お客様に対する紛争に係る通知は、当社に登録されている最新のお客様の住所に送られます。そのため、お客様は、登録している住所に変更があった場合、当社に対してEメールEAILegal@ea.epson.comまたは当社宛先への書面による通知によって、住所変更を通知することが重要です。紛争に係る通知には、送信者の氏名、住所、問合せ先、紛争の原因たる事実および求める救済が含まれている必要があります(以下「紛争通知」といいます)。紛争通知を受領後、お客様および当社は、仲裁開始前に紛争解決に向けて誠実に行動することに同意します。
+
+22.4 少額裁判所 前述の規定にかかわらず、お客様は、お客様の州または自治体の少額裁判所に対して個別の訴訟を提訴することができます。ただし、当該訴訟が当該少額裁判所の管轄下であり、かつ当該少額裁判所でのみ係属中である場合に限ります。
+
+22.5 集団訴訟および集団仲裁の放棄 お客様および当社は、紛争に関して、両当事者とも相手方に個人としての資格においてのみ訴訟を提起することができ、州または連邦の集団訴訟および集団仲裁を含むいかなる集団訴訟および代表訴訟の原告およびクラスメンバーとしては訴訟を提起できないことに同意します。集団訴訟、集団仲裁、私的司法長官訴訟その他ある者が代表者としての資格により行為するいかなる手続も認められません。したがって、本条に規定されている仲裁手続において、仲裁人は、仲裁手続に関して影響を受ける全ての当事者の書面による承諾なしに当該仲裁手続を併合することはできません。
+
+22.6 仲裁手続き お客様または当社が仲裁手続きを開始した場合、当該仲裁は仲裁が申立てられた時点において有効なJAMSの仲裁規則(ただし、集団での仲裁や代表として提起する仲裁を許容するルールは除くものとし、以下「JAMS仲裁規則」といいます。)およびこの契約の定めに従って行われます。なお、JAMS仲裁規則については、http://www.jamsadr.com の閲覧か1-800-352-5267への問合せにより参照可能です。全ての紛争は両当事者にとって中立な、単独の仲裁人によって解決され、両当事者は当該仲裁人を選定する合理的な機会を有します。仲裁人はこの契約の条項に拘束されます。この契約の解釈、適用可能性、執行可能性もしくは成立に起因し、またはそれらに関連する全ての紛争(この契約の全部または一部が無効または取消可能であるという訴えを含みます)は、いかなる連邦、州、地域の裁判所や機関ではなく、当該仲裁人のみがこれを解決する唯一の権限を有するものとします。ここで定められた仲裁人への広範な権限委任にかかわらず、裁判所は、請求または請求の原因が前述の第22条第1項において紛争の定義から除外された知的財産権に関する請求であるかどうかという限定的な問題につき判断することができます。仲裁人は、法律または衡平法に基づき、裁判所において認められる救済を付与する権限を与えられます。仲裁人は、裁判所と同様に賠償請求をお客様に認めることができます。仲裁人は、救済を求める個別のお客様のみに、当該お客様の個別の訴えにより認められる救済を提供するのに必要な範囲のみにおいて、宣言的救済または差止め命令による救済を与えることができます。仲裁手続きの費用は訴訟にかかる費用を上回る場合があり、また、仲裁におけるディスカバリの権利は訴訟手続きに比較して限定されたものとなる可能性があります。仲裁人による仲裁判断は拘束力があり、管轄を有する裁判所において判決とされる可能性があります。
+
+お客様は、仲裁のヒアリングに電話で参加することを選択することができます。電話による仲裁のヒアリングを希望されない場合は、お客様の主たるお住まいから合理的に参加可能な場所か、カリフォルニア州オレンジ郡のいずれかでの開催をお客様が選択することができます。
+
+a) 仲裁手続きの開始 お客様または当社が紛争を仲裁によって解決することを選択した場合、両当事者は以下の手続きに従うものとします。
+(i) 仲裁申立書を作成します。仲裁申立書には、紛争の概要説明と支払を求める損害の総額を必ず記載します。仲裁申立書のフォーム(以下「仲裁申立書」といいます)はhttp://www.jamsadr.comにて確認することができます。
+(ii) 仲裁申立書を3部、必要な仲裁申立費用を添えて、以下の宛先に提出します。
+JAMS, 500 North State College Blvd., Suite 600 Orange, CA 92868, U.S.A.
+(iii) 仲裁申立書1部を相手方(紛争通知に記載されているのと同じ宛先)または別途両当事者によって合意された宛先に送付します。
+b) ヒアリングの進め方 仲裁手続き中では、仲裁人がお客様または当社が権利を有する額を決定するまでは、一切の和解提案の額を仲裁人に開示してはならないものとします。仲裁手続き中において、紛争に関連し、弁護士秘匿特権で保護されていない情報のディスカバリや交換が認められる場合があります。
+c) 仲裁費用 当社は、この契約の条項に従い、(お客様または当社によって)開始した仲裁のための全てのJAMSの仲裁申立費用や仲裁人費用を支払い、お客様が当該費用を負担された場合はこれを払い戻します。
+d) お客様有利の仲裁決定 お客様または当社がUS$75,000以下(弁護士報酬および費用を除きます)の損害賠償を求める紛争において、仲裁人の仲裁判断が、当社から提示した書面による最終和解案よりも高い額をお客様に与えるものであった場合、当社は、(i)US$1,000または仲裁判断で示された金額のうち高い方の金額をお客様に支払い、(ii)(もし発生していれば)お客様が負担する合理的な弁護士報酬の2倍の金額をお客様に支払い、(iii)お客様の弁護士が仲裁に至った紛争の調査・準備や遂行のために要した合理的な範囲のあらゆる経費(専門家証人の報酬および費用を含みます。)を払い戻します。お客様と当社との間の書面より合意された場合を除き、仲裁人はこの第22条第6項(d)に従って当社が支払うべき報酬、費用および経費を決定するものとします。
+e) 弁護士報酬 当社は、この契約の下での紛争に関して開始された仲裁手続きにおいて発生した弁護士報酬および経費を請求しません。上記の第22条第6項(d)に基づく弁護士報酬および経費に関するお客様の権利は、適用可能な法に基づく弁護士報酬および経費に関するお客様の権利を制限するものではありません。上記にかかわらず、仲裁人は弁護士報酬および経費を二重に与える内容の仲裁判断を下すことはできないものとします。
+
+22.7 オプトアウト お客様は、お客様がこの契約に同意(ソフトウェアを購入、ダウンロード、インストールすること、その他当社製品、製品、サービスを適切に利用することを含みますが、これに限られません。)してから30日以内に当社宛先に以下の項目を明記した書面を送付することで、この契約で規定される最終的で拘束力のある個別仲裁手続きならびに集団および代表訴訟手続きの放棄からのオプトアウト(お客様自身を除外すること)を選択することができます。
+(ⅰ)お客様の氏名
+(ⅱ)お客様のメールアドレス
+(ⅲ)お客様が第22条に規定される最終的で拘束力のある個別仲裁手続きならびに集団および代表訴訟手続きの放棄から除外されることを希望すること
+
+お客様が以上の手順に従ってオプトアウトを選択した場合でも、訴訟の事前通知義務を含むその他の全ての規定は引き続き適用されます。
+
+22.8 第22条の変更 この契約の規定にかかわらず、お客様および当社は、当社がこの契約に規定する紛争解決手続きおよび集団訴訟の放棄に関する条項を将来的に変更する場合(当社の住所変更を除く)、当社がお客様から当該変更について同意する旨の積極的な意思表示を得ることに同意します。
+お客様が当該変更について同意する旨の積極的な意思表示を行わない場合、お客様は、第22条の規定に従いお客様と当社の間の紛争を仲裁により解決すること(または、お客様が最初にこの契約書に同意した際にオプトアウトを適時に選択した場合は、第22条第7項の規定に基づき紛争を解決すること)に同意するものとします。
+
+22.9 分離可能性 第22条のいずれかの規定が法的強制力を持たないことが判明した場合、当該規定はこの契約のその他の規定が完全に有効に存続するように、この契約のその他の規定から分離されるものとします。本項の規定は第22条第5項に規定する集団訴訟または代表訴訟の禁止に対しては適用されないものとし、第22条第5項が無効であることが判明した場合、第22条全体(第22条のみに限る)が無効となります。
+
+23 米国ニュージャージー州に居住のお客様へ この契約の規定にかかわらず、第7条または第8条のいずれかの規定がニュージャージー州法において法的執行力を有さず、無効であり、または適用されないと判断された場合、それらの規定はお客様には適用されませんが、この契約のその他の規定は引き続きお客様と当社に適用されます。この契約の規定にかかわらず、この契約の規定はお客様のニュージャージー州のTRUTH-IN-CONSUMER CONTRACT, WARRANTY AND NOTICE ACTにより付与される権利を制限するものではなく、そのようにみなされ、解釈されてはなりません。
+
+2018年12月版 \ No newline at end of file
diff --git a/non-free/Makefile.am b/non-free/Makefile.am
new file mode 100644
index 0000000..ea243e4
--- /dev/null
+++ b/non-free/Makefile.am
@@ -0,0 +1,88 @@
+## Makefile.am -- an automake template for a Makefile.in file
+## Copyright (C) 2019 SEIKO EPSON Corporation
+##
+## This file is part of the "Image Scan!" build infra-structure.
+##
+## The "Image Scan!" build infra-structure is free software.
+## You can redistribute it and/or modify it under the terms of the GNU
+## General Public License as published by the Free Software Foundation;
+## either version 2 of the License or at your option any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+## FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+## See the GNU General Public License for more details.
+##
+## You should have received a verbatim copy of the GNU General Public
+## License along with this program; if not, write to:
+##
+## Free Software Foundation, Inc.
+## 59 Temple Place, Suite 330
+## Boston, MA 02111-1307 USA
+
+NON_FREE_INCLUDES = \
+ esmod.hh
+NON_FREE_FRONTEND_SOLIBS = \
+ libesmod-i386.so \
+ libesmod-i386.c2.so \
+ libesmod-x86_64.c2.so
+NON_FREE_LICENSES = \
+ COPYING.EPSON.en.txt \
+ COPYING.EPSON.ja.txt
+
+EXTRA_DIST = \
+ checksums.md5 \
+ $(NON_FREE_LICENSES) \
+ $(NON_FREE_FRONTEND_SOLIBS) \
+ $(NON_FREE_INCLUDES)
+
+
+V_MAJOR = 2
+V_MINOR = 0
+V_REV = 0
+
+
+all: checksum $(NON_FREE_SOLIBS)
+
+checksum: $(srcdir)/checksums.md5
+ @(cd $(<D); \
+ if md5sum --status -c $(<F); then \
+ : ; \
+ else \
+ echo "ERROR: inconsistent set of header file and libraries"; \
+ exit 1; \
+ fi)
+
+if ENABLE_FRONTEND
+
+NON_FREE_SOLIBS = libesmod.so
+
+$(NON_FREE_SOLIBS):
+ -rm -f $@
+ case $(ISCAN_HOST_CPU) in \
+ i?86) arch=i386;; \
+ x86_64) arch=x86_64;; \
+ *) echo "unsupported architecture" 1>&2; \
+ exit 1;; \
+ esac; \
+ $(LN_S) $(srcdir)/libesmod-$${arch}$(PACKAGE_CXX_ABI).so $@
+
+install-exec-local: $(NON_FREE_SOLIBS)
+ $(mkinstalldirs) $(DESTDIR)$(libdir)
+ @list="$(NON_FREE_SOLIBS)"; for nfl in $$list; do \
+ echo installing $${nfl} in $(libdir)/$${nfl}...; \
+ $(LIBTOOL) --mode=install $(INSTALL_PROGRAM) $(srcdir)/$${nfl} \
+ $(DESTDIR)$(libdir)/$${nfl}.$(V_MAJOR).$(V_MINOR).$(V_REV); \
+ (cd $(DESTDIR)$(libdir) \
+ && ln -fs $${nfl}.$(V_MAJOR).$(V_MINOR).$(V_REV) $${nfl}.$(V_MAJOR)); \
+ done
+
+uninstall-local:
+ @list="$(NON_FREE_SOLIBS)"; for nfl in $$list; do \
+ rm -f $(DESTDIR)$(libdir)/$${nfl}.$(V_MAJOR); \
+ rm -f $(DESTDIR)$(libdir)/$${nfl}.$(V_MAJOR).$(V_MINOR).$(V_REV); \
+ done
+
+endif
+
+.PHONY: checksum $(NON_FREE_SOLIBS)
diff --git a/non-free/checksums.md5 b/non-free/checksums.md5
new file mode 100644
index 0000000..b6351d3
--- /dev/null
+++ b/non-free/checksums.md5
@@ -0,0 +1,4 @@
+a2db5df8859bc9723129c088bbea4729 esmod.hh
+f26bac3172fee9ddd9efb04214ee13f1 libesmod-i386.c2.so
+c3d9ed06a75c7da7e276d77597408f51 libesmod-i386.so
+d562e093122678918fe527d932e919bf libesmod-x86_64.c2.so
diff --git a/non-free/esmod.hh b/non-free/esmod.hh
new file mode 100644
index 0000000..c1ce40b
--- /dev/null
+++ b/non-free/esmod.hh
@@ -0,0 +1,177 @@
+// esmod.hh -- exposed API of the 'esmod' library
+// Copyright (C) 2019 SEIKO EPSON Corporation
+//
+// This file is part of the 'esmod' library.
+//
+// The 'esmod' library is non-free software.
+// It is distributed under the terms of the EPSON END USER SOFTWARE LICENSE.
+//
+// You should have received a verbatim copy of the EPSON END USER
+// SOFTWARE LICENSE along with the software.
+
+
+#ifndef esmod_hh_included
+#define esmod_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#include <cstddef> /* for size_t */
+
+namespace esmod
+{
+
+#define ESMOD_OPTION_FLATBED (0)
+#define ESMOD_OPTION_TPU (1)
+#define ESMOD_OPTION_ADF (2)
+
+#define ESMOD_FILM_NEGATIVE (0)
+#define ESMOD_FILM_POSITIVE (1)
+
+#define ESMOD_PIXEL_RGB (0)
+#define ESMOD_PIXEL_GRAY (1)
+#define ESMOD_PIXEL_MONO (2)
+
+#define ESMOD_IMAGE_PHOTO (0)
+#define ESMOD_IMAGE_DOCUMENT (1)
+#define ESMOD_IMAGE_LINE_ART (2)
+
+#define ESMOD_DEFAULT_GAMMA (1.0)
+#define ESMOD_DEFAULT_HILITE (245)
+#define ESMOD_DEFAULT_SHADOW (8)
+#define ESMOD_DEFAULT_THRESHOLD (160)
+
+#define ESMOD_FOCUS_UMASK (1)
+#define ESMOD_FOCUS_GAUSS (2)
+#define ESMOD_FOCUS_UMASK_Y (3)
+
+#define ESMOD_SCALE_NEAREST_NEIGHBOUR (1)
+#define ESMOD_SCALE_BILINEAR (2)
+#define ESMOD_SCALE_BICUBIC (3)
+
+
+typedef unsigned char byte_type;
+typedef size_t size_type;
+typedef bool bool_type;
+typedef int type_type;
+typedef long long_type;
+typedef unsigned long parm_type;
+typedef double real_type;
+
+
+class filter
+{
+public:
+ virtual ~filter () {};
+
+ //! Returns a block of n bytes of post-filter data.
+ virtual filter& getblock ( byte_type *block, size_type n) = 0;
+ //! Feeds the filter a block of n bytes of data.
+ virtual filter& putblock (const byte_type *block, size_type n) = 0;
+ //! Combines putblock() and getblock() in a single call.
+ virtual filter& exec (const byte_type *i_block, size_type i_n,
+ byte_type *o_block, size_type o_n)
+ {
+ putblock (i_block, i_n);
+ return getblock (o_block, o_n);
+ }
+
+ //! Returns number of additional input lines needed for out_lines of output.
+ virtual size_type get_line_quote (size_type out_lines)
+ { return out_lines; };
+
+protected:
+ void *_hidden_data;
+ filter () {};
+
+private: // undefined to prevent copying
+ filter (const filter&);
+ filter& operator= (const filter&);
+};
+
+class focus : public filter
+{
+public:
+ focus (long_type width, long_type height, long_type rowbytes,
+ size_type bits_per_pixel);
+ focus (long_type i_width, long_type i_height, long_type i_rowbytes,
+ long_type o_width, long_type o_height, long_type o_rowbytes,
+ size_type bits_per_pixel,
+ parm_type strength, parm_type radius, parm_type clipping,
+ type_type focus_type);
+ virtual ~focus ();
+
+ void set_parms (size_type resolution, bool_type film_type, bool_type is_dumb,
+ parm_type *strength, parm_type *radius, parm_type *clipping);
+
+ virtual filter& getblock ( byte_type *block, size_type n);
+ virtual filter& putblock (const byte_type *block, size_type n);
+ virtual filter& exec (const byte_type *i_block, size_type i_n,
+ byte_type *o_block, size_type o_n);
+
+ virtual size_type get_line_quote (size_type out_lines);
+};
+
+class moire : public filter
+{
+public:
+ moire (long_type i_width, long_type i_height, long_type i_rowbytes,
+ long_type o_width, long_type o_height, long_type o_rowbytes,
+ size_type bits_per_pixel,
+ size_type resolution, bool_type is_dumb);
+ virtual ~moire ();
+
+ virtual filter& getblock ( byte_type *block, size_type n);
+ virtual filter& putblock (const byte_type *block, size_type n);
+ virtual filter& exec (const byte_type *i_block, size_type i_n,
+ byte_type *o_block, size_type o_n);
+
+ static size_type get_res_quote (size_type out_res, bool_type is_dumb);
+ virtual size_type get_line_quote (size_type out_lines);
+};
+
+class scale : public filter
+{
+public:
+ scale (long_type i_width, long_type i_height, long_type i_rowbytes,
+ long_type o_width, long_type o_height, long_type o_rowbytes,
+ size_type bits_per_pixel,
+ type_type scale_type);
+ virtual ~scale ();
+
+ virtual filter& getblock ( byte_type *block, size_type n);
+ virtual filter& putblock (const byte_type *block, size_type n);
+ virtual filter& exec (const byte_type *i_block, size_type i_n,
+ byte_type *o_block, size_type o_n);
+
+ virtual size_type get_line_quote (size_type out_lines);
+};
+
+// WARNING: These quite likely modify global state in libesmod.
+void auto_expose (type_type option_type, type_type film_type,
+ byte_type *image,
+ long_type width, long_type height, long_type rowbytes,
+ long_type top, long_type left,
+ long_type bottom, long_type right,
+ long_type *gamma, long_type *hilite,
+ long_type *shadow, long_type *graybalance,
+ real_type film_gamma[3],
+ real_type film_yp[3],
+ real_type grayl[3],
+ bool_type is_photo, bool_type is_dumb);
+void build_LUT (type_type option_type, type_type film_type,
+ type_type pixel_type, type_type image_type,
+ long_type gamma, long_type hilite,
+ long_type shadow, long_type graybalance,
+ const real_type film_gamma[3],
+ const real_type film_yp[3],
+ const real_type grayl[3],
+ byte_type master[256],
+ byte_type rgb[3 * 256],
+ byte_type lut[3 * 256],
+ bool is_dumb);
+
+} // namespace esmod
+
+#endif /* !defined (esmod_hh_included) */
diff --git a/non-free/libesmod-i386.c2.so b/non-free/libesmod-i386.c2.so
new file mode 100755
index 0000000..3f03640
--- /dev/null
+++ b/non-free/libesmod-i386.c2.so
Binary files differ
diff --git a/non-free/libesmod-i386.so b/non-free/libesmod-i386.so
new file mode 100755
index 0000000..8148239
--- /dev/null
+++ b/non-free/libesmod-i386.so
Binary files differ
diff --git a/non-free/libesmod-x86_64.c2.so b/non-free/libesmod-x86_64.c2.so
new file mode 100755
index 0000000..f18aa73
--- /dev/null
+++ b/non-free/libesmod-x86_64.c2.so
Binary files differ
diff --git a/po/LINGUAS b/po/LINGUAS
new file mode 100644
index 0000000..979d45b
--- /dev/null
+++ b/po/LINGUAS
@@ -0,0 +1,22 @@
+# po/LINGUAS -- set of available languages/locales
+# Copyright (C) 2004, 2009 SEIKO EPSON CORPORATION
+
+# Officially supported languages/locales
+de
+es
+fr
+it
+ja
+ko
+nl
+pt
+zh_CN
+zh_TW
+
+# Contributed languages/locales
+
+ar
+
+# Language/locale variants courtesy of 'gettext'
+en@quot
+en@boldquot
diff --git a/po/Makefile.in.in b/po/Makefile.in.in
new file mode 100644
index 0000000..53efc5e
--- /dev/null
+++ b/po/Makefile.in.in
@@ -0,0 +1,366 @@
+# Makefile for PO directory in any package using GNU gettext.
+# Copyright (C) 1995-1997, 2000-2004 by Ulrich Drepper <drepper@gnu.ai.mit.edu>
+#
+# This file can be copied and used freely without restrictions. It can
+# be used in projects which are not available under the GNU General Public
+# License but which still want to provide support for the GNU gettext
+# functionality.
+# Please note that the actual code of GNU gettext is covered by the GNU
+# General Public License and is *not* in the public domain.
+#
+# Origin: gettext-0.14
+
+PACKAGE = @PACKAGE@
+VERSION = @VERSION@
+
+SHELL = /bin/sh
+@SET_MAKE@
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+datadir = @datadir@
+localedir = $(datadir)/locale
+gettextsrcdir = $(datadir)/gettext/po
+
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+MKINSTALLDIRS = @MKINSTALLDIRS@
+mkinstalldirs = $(SHELL) $(MKINSTALLDIRS)
+
+GMSGFMT = @GMSGFMT@
+MSGFMT = @MSGFMT@
+XGETTEXT = @XGETTEXT@
+MSGMERGE = msgmerge
+MSGMERGE_UPDATE = @MSGMERGE@ --update
+MSGINIT = msginit
+MSGCONV = msgconv
+MSGFILTER = msgfilter
+
+POFILES = @POFILES@
+GMOFILES = @GMOFILES@
+UPDATEPOFILES = @UPDATEPOFILES@
+DUMMYPOFILES = @DUMMYPOFILES@
+DISTFILES.common = Makefile.in.in remove-potcdate.sin \
+$(DISTFILES.common.extra1) $(DISTFILES.common.extra2) $(DISTFILES.common.extra3)
+DISTFILES = $(DISTFILES.common) Makevars POTFILES.in $(DOMAIN).pot stamp-po \
+$(POFILES) $(GMOFILES) \
+$(DISTFILES.extra1) $(DISTFILES.extra2) $(DISTFILES.extra3)
+
+POTFILES = \
+
+CATALOGS = @CATALOGS@
+
+# Makevars gets inserted here. (Don't remove this line!)
+
+.SUFFIXES:
+.SUFFIXES: .po .gmo .mo .sed .sin .nop .po-create .po-update
+
+.po.mo:
+ @echo "$(MSGFMT) -c -o $@ $<"; \
+ $(MSGFMT) -c -o t-$@ $< && mv t-$@ $@
+
+.po.gmo:
+ @lang=`echo $* | sed -e 's,.*/,,'`; \
+ test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \
+ echo "$${cdcmd}rm -f $${lang}.gmo && $(GMSGFMT) -c --statistics -o $${lang}.gmo $${lang}.po"; \
+ cd $(srcdir) && rm -f $${lang}.gmo && $(GMSGFMT) -c --statistics -o t-$${lang}.gmo $${lang}.po && mv t-$${lang}.gmo $${lang}.gmo
+
+.sin.sed:
+ sed -e '/^#/d' $< > t-$@
+ mv t-$@ $@
+
+
+all: all-@USE_NLS@
+
+all-yes: stamp-po
+all-no:
+
+# stamp-po is a timestamp denoting the last time at which the CATALOGS have
+# been loosely updated. Its purpose is that when a developer or translator
+# checks out the package via CVS, and the $(DOMAIN).pot file is not in CVS,
+# "make" will update the $(DOMAIN).pot and the $(CATALOGS), but subsequent
+# invocations of "make" will do nothing. This timestamp would not be necessary
+# if updating the $(CATALOGS) would always touch them; however, the rule for
+# $(POFILES) has been designed to not touch files that don't need to be
+# changed.
+stamp-po: $(srcdir)/$(DOMAIN).pot
+ test -z "$(GMOFILES)" || $(MAKE) $(GMOFILES)
+ @echo "touch stamp-po"
+ @echo timestamp > stamp-poT
+ @mv stamp-poT stamp-po
+
+# Note: Target 'all' must not depend on target '$(DOMAIN).pot-update',
+# otherwise packages like GCC can not be built if only parts of the source
+# have been downloaded.
+
+# This target rebuilds $(DOMAIN).pot; it is an expensive operation.
+# Note that $(DOMAIN).pot is not touched if it doesn't need to be changed.
+$(DOMAIN).pot-update: $(POTFILES) $(srcdir)/POTFILES.in remove-potcdate.sed
+ $(XGETTEXT) --default-domain=$(DOMAIN) --directory=$(top_srcdir) \
+ --add-comments=TRANSLATORS: $(XGETTEXT_OPTIONS) \
+ --files-from=$(srcdir)/POTFILES.in \
+ --copyright-holder='$(COPYRIGHT_HOLDER)' \
+ --msgid-bugs-address='$(MSGID_BUGS_ADDRESS)'
+ test ! -f $(DOMAIN).po || { \
+ if test -f $(srcdir)/$(DOMAIN).pot; then \
+ sed -f remove-potcdate.sed < $(srcdir)/$(DOMAIN).pot > $(DOMAIN).1po && \
+ sed -f remove-potcdate.sed < $(DOMAIN).po > $(DOMAIN).2po && \
+ if cmp $(DOMAIN).1po $(DOMAIN).2po >/dev/null 2>&1; then \
+ rm -f $(DOMAIN).1po $(DOMAIN).2po $(DOMAIN).po; \
+ else \
+ rm -f $(DOMAIN).1po $(DOMAIN).2po $(srcdir)/$(DOMAIN).pot && \
+ mv $(DOMAIN).po $(srcdir)/$(DOMAIN).pot; \
+ fi; \
+ else \
+ mv $(DOMAIN).po $(srcdir)/$(DOMAIN).pot; \
+ fi; \
+ }
+
+# This rule has no dependencies: we don't need to update $(DOMAIN).pot at
+# every "make" invocation, only create it when it is missing.
+# Only "make $(DOMAIN).pot-update" or "make dist" will force an update.
+$(srcdir)/$(DOMAIN).pot:
+ $(MAKE) $(DOMAIN).pot-update
+
+# This target rebuilds a PO file if $(DOMAIN).pot has changed.
+# Note that a PO file is not touched if it doesn't need to be changed.
+$(POFILES): $(srcdir)/$(DOMAIN).pot
+ @lang=`echo $@ | sed -e 's,.*/,,' -e 's/\.po$$//'`; \
+ if test -f "$(srcdir)/$${lang}.po"; then \
+ test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \
+ echo "$${cdcmd}$(MSGMERGE_UPDATE) $${lang}.po $(DOMAIN).pot"; \
+ cd $(srcdir) && $(MSGMERGE_UPDATE) $${lang}.po $(DOMAIN).pot; \
+ else \
+ $(MAKE) $${lang}.po-create; \
+ fi
+
+
+install: install-exec install-data
+install-exec:
+install-data: install-data-@USE_NLS@
+ if test "$(PACKAGE)" = "gettext-tools"; then \
+ $(mkinstalldirs) $(DESTDIR)$(gettextsrcdir); \
+ for file in $(DISTFILES.common) Makevars.template; do \
+ $(INSTALL_DATA) $(srcdir)/$$file \
+ $(DESTDIR)$(gettextsrcdir)/$$file; \
+ done; \
+ for file in Makevars; do \
+ rm -f $(DESTDIR)$(gettextsrcdir)/$$file; \
+ done; \
+ else \
+ : ; \
+ fi
+install-data-no: all
+install-data-yes: all
+ $(mkinstalldirs) $(DESTDIR)$(datadir)
+ @catalogs='$(CATALOGS)'; \
+ for cat in $$catalogs; do \
+ cat=`basename $$cat`; \
+ lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \
+ dir=$(localedir)/$$lang/LC_MESSAGES; \
+ $(mkinstalldirs) $(DESTDIR)$$dir; \
+ if test -r $$cat; then realcat=$$cat; else realcat=$(srcdir)/$$cat; fi; \
+ $(INSTALL_DATA) $$realcat $(DESTDIR)$$dir/$(DOMAIN).mo; \
+ echo "installing $$realcat as $(DESTDIR)$$dir/$(DOMAIN).mo"; \
+ for lc in '' $(EXTRA_LOCALE_CATEGORIES); do \
+ if test -n "$$lc"; then \
+ if (cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc 2>/dev/null) | grep ' -> ' >/dev/null; then \
+ link=`cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc | sed -e 's/^.* -> //'`; \
+ mv $(DESTDIR)$(localedir)/$$lang/$$lc $(DESTDIR)$(localedir)/$$lang/$$lc.old; \
+ mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \
+ (cd $(DESTDIR)$(localedir)/$$lang/$$lc.old && \
+ for file in *; do \
+ if test -f $$file; then \
+ ln -s ../$$link/$$file $(DESTDIR)$(localedir)/$$lang/$$lc/$$file; \
+ fi; \
+ done); \
+ rm -f $(DESTDIR)$(localedir)/$$lang/$$lc.old; \
+ else \
+ if test -d $(DESTDIR)$(localedir)/$$lang/$$lc; then \
+ :; \
+ else \
+ rm -f $(DESTDIR)$(localedir)/$$lang/$$lc; \
+ mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \
+ fi; \
+ fi; \
+ rm -f $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \
+ ln -s ../LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo 2>/dev/null || \
+ ln $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo 2>/dev/null || \
+ cp -p $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \
+ echo "installing $$realcat link as $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo"; \
+ fi; \
+ done; \
+ done
+
+install-strip: install
+
+installdirs: installdirs-exec installdirs-data
+installdirs-exec:
+installdirs-data: installdirs-data-@USE_NLS@
+ if test "$(PACKAGE)" = "gettext-tools"; then \
+ $(mkinstalldirs) $(DESTDIR)$(gettextsrcdir); \
+ else \
+ : ; \
+ fi
+installdirs-data-no:
+installdirs-data-yes:
+ $(mkinstalldirs) $(DESTDIR)$(datadir)
+ @catalogs='$(CATALOGS)'; \
+ for cat in $$catalogs; do \
+ cat=`basename $$cat`; \
+ lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \
+ dir=$(localedir)/$$lang/LC_MESSAGES; \
+ $(mkinstalldirs) $(DESTDIR)$$dir; \
+ for lc in '' $(EXTRA_LOCALE_CATEGORIES); do \
+ if test -n "$$lc"; then \
+ if (cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc 2>/dev/null) | grep ' -> ' >/dev/null; then \
+ link=`cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc | sed -e 's/^.* -> //'`; \
+ mv $(DESTDIR)$(localedir)/$$lang/$$lc $(DESTDIR)$(localedir)/$$lang/$$lc.old; \
+ mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \
+ (cd $(DESTDIR)$(localedir)/$$lang/$$lc.old && \
+ for file in *; do \
+ if test -f $$file; then \
+ ln -s ../$$link/$$file $(DESTDIR)$(localedir)/$$lang/$$lc/$$file; \
+ fi; \
+ done); \
+ rm -f $(DESTDIR)$(localedir)/$$lang/$$lc.old; \
+ else \
+ if test -d $(DESTDIR)$(localedir)/$$lang/$$lc; then \
+ :; \
+ else \
+ rm -f $(DESTDIR)$(localedir)/$$lang/$$lc; \
+ mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \
+ fi; \
+ fi; \
+ fi; \
+ done; \
+ done
+
+# Define this as empty until I found a useful application.
+installcheck:
+
+uninstall: uninstall-exec uninstall-data
+uninstall-exec:
+uninstall-data: uninstall-data-@USE_NLS@
+ if test "$(PACKAGE)" = "gettext-tools"; then \
+ for file in $(DISTFILES.common) Makevars.template; do \
+ rm -f $(DESTDIR)$(gettextsrcdir)/$$file; \
+ done; \
+ else \
+ : ; \
+ fi
+uninstall-data-no:
+uninstall-data-yes:
+ catalogs='$(CATALOGS)'; \
+ for cat in $$catalogs; do \
+ cat=`basename $$cat`; \
+ lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \
+ for lc in LC_MESSAGES $(EXTRA_LOCALE_CATEGORIES); do \
+ rm -f $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \
+ done; \
+ done
+
+check: all
+
+info dvi ps pdf html tags TAGS ctags CTAGS ID:
+
+mostlyclean:
+ rm -f remove-potcdate.sed
+ rm -f stamp-poT
+ rm -f core core.* $(DOMAIN).po $(DOMAIN).1po $(DOMAIN).2po *.new.po
+ rm -fr *.o
+
+clean: mostlyclean
+
+distclean: clean
+ rm -f Makefile Makefile.in POTFILES *.mo
+
+maintainer-clean: distclean
+ @echo "This command is intended for maintainers to use;"
+ @echo "it deletes files that may require special tools to rebuild."
+ rm -f stamp-po $(GMOFILES)
+
+distdir = $(top_builddir)/$(PACKAGE)-$(VERSION)/$(subdir)
+dist distdir:
+ $(MAKE) update-po
+ @$(MAKE) dist2
+# This is a separate target because 'update-po' must be executed before.
+dist2: $(DISTFILES)
+ dists="$(DISTFILES)"; \
+ if test "$(PACKAGE)" = "gettext-tools"; then \
+ dists="$$dists Makevars.template"; \
+ fi; \
+ if test -f $(srcdir)/ChangeLog; then \
+ dists="$$dists ChangeLog"; \
+ fi; \
+ for i in 0 1 2 3 4 5 6 7 8 9; do \
+ if test -f $(srcdir)/ChangeLog.$$i; then \
+ dists="$$dists ChangeLog.$$i"; \
+ fi; \
+ done; \
+ if test -f $(srcdir)/LINGUAS; then dists="$$dists LINGUAS"; fi; \
+ for file in $$dists; do \
+ if test -f $$file; then \
+ cp -p $$file $(distdir); \
+ else \
+ cp -p $(srcdir)/$$file $(distdir); \
+ fi; \
+ done
+
+update-po: Makefile
+ $(MAKE) $(DOMAIN).pot-update
+ test -z "$(UPDATEPOFILES)" || $(MAKE) $(UPDATEPOFILES)
+ $(MAKE) update-gmo
+
+# General rule for creating PO files.
+
+.nop.po-create:
+ @lang=`echo $@ | sed -e 's/\.po-create$$//'`; \
+ echo "File $$lang.po does not exist. If you are a translator, you can create it through 'msginit'." 1>&2; \
+ exit 1
+
+# General rule for updating PO files.
+
+.nop.po-update:
+ @lang=`echo $@ | sed -e 's/\.po-update$$//'`; \
+ if test "$(PACKAGE)" = "gettext-tools"; then PATH=`pwd`/../src:$$PATH; fi; \
+ tmpdir=`pwd`; \
+ echo "$$lang:"; \
+ test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \
+ echo "$${cdcmd}$(MSGMERGE) $$lang.po $(DOMAIN).pot -o $$lang.new.po"; \
+ cd $(srcdir); \
+ if $(MSGMERGE) $$lang.po $(DOMAIN).pot -o $$tmpdir/$$lang.new.po; then \
+ if cmp $$lang.po $$tmpdir/$$lang.new.po >/dev/null 2>&1; then \
+ rm -f $$tmpdir/$$lang.new.po; \
+ else \
+ if mv -f $$tmpdir/$$lang.new.po $$lang.po; then \
+ :; \
+ else \
+ echo "msgmerge for $$lang.po failed: cannot move $$tmpdir/$$lang.new.po to $$lang.po" 1>&2; \
+ exit 1; \
+ fi; \
+ fi; \
+ else \
+ echo "msgmerge for $$lang.po failed!" 1>&2; \
+ rm -f $$tmpdir/$$lang.new.po; \
+ fi
+
+$(DUMMYPOFILES):
+
+update-gmo: Makefile $(GMOFILES)
+ @:
+
+Makefile: Makefile.in.in $(top_builddir)/config.status @POMAKEFILEDEPS@
+ cd $(top_builddir) \
+ && CONFIG_FILES=$(subdir)/$@.in CONFIG_HEADERS= \
+ $(SHELL) ./config.status
+
+force:
+
+# Tell versions [3.59,3.63) of GNU make not to export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/po/Makevars b/po/Makevars
new file mode 100644
index 0000000..ae538fe
--- /dev/null
+++ b/po/Makevars
@@ -0,0 +1,41 @@
+# Makevars -- Makefile snippets for inclusion in po/Makefile
+
+# Usually the message domain is the same as the package name.
+DOMAIN = $(PACKAGE)
+
+# These two variables depend on the location of this directory.
+subdir = po
+top_builddir = ..
+
+# These options get passed to xgettext.
+XGETTEXT_OPTIONS = --keyword=_ --keyword=N_
+
+# This is the copyright holder that gets inserted into the header of the
+# $(DOMAIN).pot file. Set this to the copyright holder of the surrounding
+# package. (Note that the msgstr strings, extracted from the package's
+# sources, belong to the copyright holder of the package.) Translators are
+# expected to transfer the copyright for their translations to this person
+# or entity, or to disclaim their copyright. The empty string stands for
+# the public domain; in this case the translators are expected to disclaim
+# their copyright.
+COPYRIGHT_HOLDER = SEIKO EPSON CORPORATION
+
+# This is the email address or URL to which the translators shall report
+# bugs in the untranslated strings:
+# - Strings which are not entire sentences, see the maintainer guidelines
+# in the GNU gettext documentation, section 'Preparing Strings'.
+# - Strings which use unclear terms or require additional context to be
+# understood.
+# - Strings which make invalid assumptions about notation of date, time or
+# money.
+# - Pluralisation problems.
+# - Incorrect English spelling.
+# - Incorrect formatting.
+# It can be your email address, or a mailing list address where translators
+# can write to without being subscribed, or the URL of a web page through
+# which the translators can contact you.
+MSGID_BUGS_ADDRESS = linux-printer@epson.jp
+
+# This is the list of locale categories, beyond LC_MESSAGES, for which the
+# message catalogs shall be used. It is usually empty.
+EXTRA_LOCALE_CATEGORIES =
diff --git a/po/POTFILES.in b/po/POTFILES.in
new file mode 100644
index 0000000..cfff785
--- /dev/null
+++ b/po/POTFILES.in
@@ -0,0 +1,27 @@
+# POTFILES.in -- source files containing translatable strings
+# Copyright (C) 2001-2004 SEIKO EPSON CORPORATION
+
+# Package source files
+frontend/file-selector.cc
+frontend/pisa_aleart_dialog.cc
+frontend/pisa_change_unit.cc
+frontend/pisa_configuration.cc
+frontend/pisa_error.cc
+frontend/pisa_gamma_correction.cc
+frontend/pisa_gimp.cc
+frontend/pisa_image_controls.cc
+frontend/pisa_img_converter.cc
+frontend/pisa_main.cc
+frontend/pisa_main_window.cc
+frontend/pisa_marquee.cc
+frontend/pisa_preference.cc
+frontend/pisa_preview_window.cc
+frontend/pisa_progress_window.cc
+frontend/pisa_sane_scan.cc
+frontend/pisa_scan_manager.cc
+frontend/pisa_scan_selector.cc
+frontend/pisa_scan_tool.cc
+frontend/pisa_settings.cc
+frontend/pisa_tool.cc
+frontend/pisa_view_manager.cc
+frontend/xpm_data.cc
diff --git a/po/Rules-quot b/po/Rules-quot
new file mode 100644
index 0000000..9c2a995
--- /dev/null
+++ b/po/Rules-quot
@@ -0,0 +1,47 @@
+# Special Makefile rules for English message catalogs with quotation marks.
+
+DISTFILES.common.extra1 = quot.sed boldquot.sed en@quot.header en@boldquot.header insert-header.sin Rules-quot
+
+.SUFFIXES: .insert-header .po-update-en
+
+en@quot.po-create:
+ $(MAKE) en@quot.po-update
+en@boldquot.po-create:
+ $(MAKE) en@boldquot.po-update
+
+en@quot.po-update: en@quot.po-update-en
+en@boldquot.po-update: en@boldquot.po-update-en
+
+.insert-header.po-update-en:
+ @lang=`echo $@ | sed -e 's/\.po-update-en$$//'`; \
+ if test "$(PACKAGE)" = "gettext"; then PATH=`pwd`/../src:$$PATH; GETTEXTLIBDIR=`cd $(top_srcdir)/src && pwd`; export GETTEXTLIBDIR; fi; \
+ tmpdir=`pwd`; \
+ echo "$$lang:"; \
+ ll=`echo $$lang | sed -e 's/@.*//'`; \
+ LC_ALL=C; export LC_ALL; \
+ cd $(srcdir); \
+ if $(MSGINIT) -i $(DOMAIN).pot --no-translator -l $$ll -o - 2>/dev/null | sed -f $$tmpdir/$$lang.insert-header | $(MSGCONV) -t UTF-8 | $(MSGFILTER) sed -f `echo $$lang | sed -e 's/.*@//'`.sed 2>/dev/null > $$tmpdir/$$lang.new.po; then \
+ if cmp $$lang.po $$tmpdir/$$lang.new.po >/dev/null 2>&1; then \
+ rm -f $$tmpdir/$$lang.new.po; \
+ else \
+ if mv -f $$tmpdir/$$lang.new.po $$lang.po; then \
+ :; \
+ else \
+ echo "creation of $$lang.po failed: cannot move $$tmpdir/$$lang.new.po to $$lang.po" 1>&2; \
+ exit 1; \
+ fi; \
+ fi; \
+ else \
+ echo "creation of $$lang.po failed!" 1>&2; \
+ rm -f $$tmpdir/$$lang.new.po; \
+ fi
+
+en@quot.insert-header: insert-header.sin
+ sed -e '/^#/d' -e 's/HEADER/en@quot.header/g' $(srcdir)/insert-header.sin > en@quot.insert-header
+
+en@boldquot.insert-header: insert-header.sin
+ sed -e '/^#/d' -e 's/HEADER/en@boldquot.header/g' $(srcdir)/insert-header.sin > en@boldquot.insert-header
+
+mostlyclean: mostlyclean-quot
+mostlyclean-quot:
+ rm -f *.insert-header
diff --git a/po/ar.gmo b/po/ar.gmo
new file mode 100644
index 0000000..545dcb2
--- /dev/null
+++ b/po/ar.gmo
Binary files differ
diff --git a/po/ar.po b/po/ar.po
new file mode 100644
index 0000000..4f9a938
--- /dev/null
+++ b/po/ar.po
@@ -0,0 +1,468 @@
+# ar.po -- Arabic translations for `iscan' messages
+# Copyright (C) 2009, 2012 SEIKO EPSON CORPORATION
+# Khaled Hosny <khaledhosny@eglug.org>, 2009, 2011.
+msgid ""
+msgstr ""
+"Project-Id-Version: ar\n"
+"Report-Msgid-Bugs-To: linux-printer@epson.jp\n"
+"POT-Creation-Date: 2019-08-21 08:06+0900\n"
+"PO-Revision-Date: 2011-08-20 02:01+0200\n"
+"Last-Translator: Khaled Hosny <khaledhosny@eglug.org>\n"
+"Language-Team: Arabic <doc@arabeyes.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: ar\n"
+"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
+"&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
+"X-Generator: Virtaal 0.7.0\n"
+
+#: frontend/file-selector.cc:465
+msgid "Overwrite"
+msgstr "استبدل"
+
+#: frontend/file-selector.cc:465
+msgid "Cancel"
+msgstr "ألغِ"
+
+#: frontend/file-selector.cc:746
+msgid "Save Options"
+msgstr "احفظ الخيارات"
+
+#: frontend/file-selector.cc:765
+msgid "Create file with all pages"
+msgstr "أنشئ ملفا بكل الصفحات"
+
+#: frontend/file-selector.cc:768
+msgid "Binding Position (for Double-Sided Scanning)"
+msgstr "موضع الكعب (للمسح على الوجهين)"
+
+#: frontend/file-selector.cc:769
+msgid "Left"
+msgstr "يسار"
+
+#: frontend/file-selector.cc:771
+msgid "Top"
+msgstr "أعلى"
+
+#: frontend/file-selector.cc:776
+msgid "Determine File Type:"
+msgstr "حدد نوع الملف:"
+
+#: frontend/file-selector.cc:835
+msgid "Start filing at:"
+msgstr "ابدأ الملء عند:"
+
+#: frontend/file-selector.cc:858
+msgid "Number of digits:"
+msgstr "عدد الأرقام:"
+
+#: frontend/pisa_aleart_dialog.cc:73 frontend/pisa_configuration.cc:204
+#: frontend/pisa_scan_selector.cc:199
+msgid " OK "
+msgstr " موافق "
+
+#: frontend/pisa_configuration.cc:113 frontend/pisa_main_window.cc:849
+msgid "Configuration"
+msgstr "التضبيطات"
+
+#: frontend/pisa_configuration.cc:163
+msgid "Print Command"
+msgstr "أمر الطبع"
+
+#: frontend/pisa_configuration.cc:186
+msgid ""
+"In order to print, your print system must be able to handle the PNG file "
+"format directly. CUPS or Photo Image Print System (versions 1.3.1 or later) "
+"do this by default."
+msgstr ""
+"لتتمكن من الطباعة، يجب أن يدعم نظام الطباعة التعامل مع ملفان PNG مباشرة. "
+"يدعم CUPS أو Image Print System (الإصدارة 1.3.1 ومابعدها) هذا افتراضيا."
+
+#: frontend/pisa_configuration.cc:214 frontend/pisa_progress_window.cc:61
+#: frontend/pisa_scan_selector.cc:213 frontend/pisa_view_manager.cc:762
+msgid " Cancel "
+msgstr " ألغِ "
+
+#: frontend/pisa_error.cc:85
+msgid "Operation completed succesfully."
+msgstr "تمت العملية بنجاح."
+
+#: frontend/pisa_error.cc:87
+msgid "Operation is not supported."
+msgstr "العملية غير مدعومة."
+
+#: frontend/pisa_error.cc:89
+msgid "Operation was cancelled."
+msgstr "أُلغِيت العملية."
+
+#: frontend/pisa_error.cc:91
+msgid "Device is busy---retry later."
+msgstr "الجهاز مشغول---حاول لاحقا."
+
+#: frontend/pisa_error.cc:93
+msgid "Data or argument is invalid."
+msgstr "البيانات أو المعامل غير مقبول."
+
+#: frontend/pisa_error.cc:95
+msgid "No more data available (end-of-file)."
+msgstr "لا تتوفر بيانات أخرى (نهاية الملف)."
+
+#: frontend/pisa_error.cc:97
+msgid ""
+"A paper jam occured. Open the Automatic Document Feeder and remove any "
+"paper."
+msgstr "حدث تكدس للورق. افتح ملقّم المستندات الآلي وأزِل أي ورقة."
+
+#: frontend/pisa_error.cc:100
+msgid "Please load the document(s) into the Automatic Document Feeder."
+msgstr "من فضلك حمّل المستندات في ملقّم المستندات الآلي."
+
+#: frontend/pisa_error.cc:103
+msgid ""
+"The automatic document feeder or scanner unit is open.\n"
+"Please close it."
+msgstr ""
+"ملقّم المستندات الآلي أو وحدة الماسحة مفتوحة.\n"
+"أغلقها من فضلك."
+
+#: frontend/pisa_error.cc:106
+msgid "Error during device I/O."
+msgstr "عطل خرج/دخل."
+
+#: frontend/pisa_error.cc:108
+msgid "Out of memory."
+msgstr "نفذت الذاكرة."
+
+#: frontend/pisa_error.cc:110
+msgid "Access to resource has been denied."
+msgstr "مُنِع الوصول إلى المورد."
+
+#: frontend/pisa_error.cc:113
+msgid "There is not enough disk space for operation"
+msgstr "لا توجد مساحة كافية على القرص للعملية"
+
+#: frontend/pisa_error.cc:116
+msgid ""
+"Could not send command to scanner.\n"
+"Check the scanner's status."
+msgstr ""
+"تعذّر إرسال الأمر للماسحة.\n"
+"راجع حالة الماسحة."
+
+#: frontend/pisa_error.cc:120
+msgid "Scanner model not supported"
+msgstr "طراز الماسحة غير مدعوم"
+
+#: frontend/pisa_error.cc:123
+msgid ""
+"Selected area is too large for this resolution.\n"
+"Reduce the selected area or resolution."
+msgstr ""
+"المنطقة المحددة كبيرة جدا على هذه الاستبانة.\n"
+"صغّر المنطقة المحددة أو الاستبانة."
+
+#: frontend/pisa_error.cc:127 frontend/pisa_error.cc:130
+msgid "Could not create file"
+msgstr "تعذّر إنشاء الملف"
+
+#: frontend/pisa_error.cc:133
+msgid ""
+"A file with the same name already exists.\n"
+"Click \"Overwrite\" to replace the file or \"Cancel\" if you want to use "
+"another file name."
+msgstr ""
+"يوجد ملف بنفس الاسم.\n"
+"انقر على \"استبدل\" لتستبدل الملف أو \"ألغِ\" إذا أردت استخدام اسم ملف آخر."
+
+#: frontend/pisa_error.cc:138
+msgid ""
+"The Image Type setting you selected cannot be used with this resolution.\n"
+"Reduce the Resolution or Scale setting."
+msgstr ""
+"لا يمكن استخدام نوع الصورة الذي اخترته مع هذه الاستبانة.\n"
+"صغّر الاستبانة أو إعدادات التحجيم."
+
+#: frontend/pisa_error.cc:143
+msgid "Tray cover is closed. Please open the tray cover and then scan again."
+msgstr "غطاء اللوحة مغلق. من فضلك افتح الغطاء ثم أعد المسح."
+
+#: frontend/pisa_error.cc:147
+msgid ""
+"A multi page feed occurred in the auto document feeder.\n"
+"Open the cover, remove the documents, and then try again. If documents "
+"remain on the tray, remove them and then reload them."
+msgstr ""
+"حدث تلقيم لعدة مستندات في ملقّم آلي.\n"
+"افتح الغطاء وأزل المستند ثم أعد المحاولة. إذا ظلّت المستندات في اللوحة، "
+"فأزلهم ثم أعد تحميلهم."
+
+#: frontend/pisa_error.cc:156
+msgid "Unexpected error occurred"
+msgstr "حدث عطل غير متوقع"
+
+#: frontend/pisa_gamma_correction.cc:482
+msgid " Reset "
+msgstr " أعد الضبط "
+
+#: frontend/pisa_image_controls.cc:253
+msgid "Gamma:"
+msgstr "جاما:"
+
+#: frontend/pisa_image_controls.cc:268
+msgid "Highlight:"
+msgstr "الإبراز:"
+
+#: frontend/pisa_image_controls.cc:283
+msgid "Shadow:"
+msgstr "الظل:"
+
+#: frontend/pisa_image_controls.cc:298
+msgid "Threshold:"
+msgstr "العتبة:"
+
+#: frontend/pisa_image_controls.cc:331
+msgid "Brightness:"
+msgstr "السطوع:"
+
+#: frontend/pisa_image_controls.cc:354
+msgid "Contrast:"
+msgstr "التباين:"
+
+#: frontend/pisa_main_window.cc:90
+msgid "File"
+msgstr "ملف"
+
+#: frontend/pisa_main_window.cc:91
+msgid "Printer"
+msgstr "طابعة"
+
+#: frontend/pisa_main_window.cc:104
+msgid "Flatbed"
+msgstr "ماسحة مسطحة"
+
+#: frontend/pisa_main_window.cc:105
+msgid "TPU - Negative film"
+msgstr "‏TPU - فيلم سالب"
+
+#: frontend/pisa_main_window.cc:106
+msgid "TPU - Positive film"
+msgstr "‏TPU - فيلم موجب"
+
+#: frontend/pisa_main_window.cc:107
+msgid "ADF - Single-sided"
+msgstr "‏ADF - أحادي الجانب"
+
+#: frontend/pisa_main_window.cc:108
+msgid "ADF - Double-sided"
+msgstr "‏ADF - ثنائي الجانب"
+
+#: frontend/pisa_main_window.cc:121
+msgid "Color Photo"
+msgstr "صورة ملونة"
+
+#: frontend/pisa_main_window.cc:122
+msgid "Color Document"
+msgstr "مستند ملون"
+
+#: frontend/pisa_main_window.cc:123
+msgid "Black & White Photo"
+msgstr "صورة أبيض وأسود"
+
+#: frontend/pisa_main_window.cc:124
+msgid "Black & White Document"
+msgstr "مستند أبيض وأسود"
+
+#: frontend/pisa_main_window.cc:125
+msgid "Line Art"
+msgstr "فن خطّي"
+
+#: frontend/pisa_main_window.cc:169
+msgid "inches"
+msgstr "بوصة"
+
+#: frontend/pisa_main_window.cc:170
+msgid "pixels"
+msgstr "بكسل"
+
+#: frontend/pisa_main_window.cc:171
+msgid "cm"
+msgstr "سم"
+
+#: frontend/pisa_main_window.cc:192
+msgid "Off"
+msgstr "معطل"
+
+#: frontend/pisa_main_window.cc:193
+msgid "Standard"
+msgstr "قياسي"
+
+#: frontend/pisa_main_window.cc:194
+msgid "Thin"
+msgstr "رفيع"
+
+#: frontend/pisa_main_window.cc:299
+msgid "Calibration is failed."
+msgstr "اخفقت المعايرة"
+
+#: frontend/pisa_main_window.cc:325
+msgid "Cleaning is failed."
+msgstr "افقت عملية التنظيف."
+
+#: frontend/pisa_main_window.cc:831
+msgid "Scan to File"
+msgstr "امسح إلى ملف"
+
+#: frontend/pisa_main_window.cc:837
+msgid "Scan"
+msgstr "امسح"
+
+#: frontend/pisa_main_window.cc:903
+msgid "Close"
+msgstr "أغلق"
+
+#: frontend/pisa_main_window.cc:922
+msgid "Preview"
+msgstr "معاينة"
+
+#: frontend/pisa_main_window.cc:923
+msgid "Zoom"
+msgstr "قرّب"
+
+#: frontend/pisa_main_window.cc:924
+msgid "Auto Exposure"
+msgstr "تعرض تلقائي"
+
+#: frontend/pisa_main_window.cc:927
+msgid "enable Start button"
+msgstr "فعّل زر البدء"
+
+#: frontend/pisa_main_window.cc:928
+msgid "Speed priority scanning"
+msgstr "مسح مع أولوية للسرعة"
+
+#: frontend/pisa_main_window.cc:930
+msgid "Document Size - Auto Detect"
+msgstr "حجم المستند - تعرف تلقائي"
+
+#: frontend/pisa_main_window.cc:932
+msgid "Correct Document Skew"
+msgstr "أصلِح انحراف المستند"
+
+#: frontend/pisa_main_window.cc:937
+msgid "Unsharp mask"
+msgstr "قناع التنعيم"
+
+#: frontend/pisa_main_window.cc:941
+msgid "Detect Double Feed (Paper Thickness)"
+msgstr "اكتشف التلقيم المزدوج (سُمك الورق)"
+
+#: frontend/pisa_main_window.cc:1027
+msgid "Scan to Print"
+msgstr "امسح إلى طابعة"
+
+#: frontend/pisa_main_window.cc:1073
+msgid "Destination:"
+msgstr "الوِجهة:"
+
+#: frontend/pisa_main_window.cc:1152 frontend/pisa_scan_selector.cc:142
+msgid "Scanner:"
+msgstr "الماسحة:"
+
+#: frontend/pisa_main_window.cc:1175
+msgid "Document"
+msgstr "المستند"
+
+#: frontend/pisa_main_window.cc:1186
+msgid "Image Controls"
+msgstr "متحكمات الصورة"
+
+#: frontend/pisa_main_window.cc:1195
+msgid "Tone Correction"
+msgstr "تصحيح الجودة"
+
+#: frontend/pisa_main_window.cc:1233
+msgid "Document Source:"
+msgstr "مصدر المستند:"
+
+#: frontend/pisa_main_window.cc:1266
+msgid "Image Type:"
+msgstr "نوع الصورة:"
+
+#: frontend/pisa_main_window.cc:1278
+msgid "Resolution:"
+msgstr "الاستبانة:"
+
+#: frontend/pisa_main_window.cc:1327
+msgid "Target"
+msgstr "الهدف"
+
+#: frontend/pisa_main_window.cc:1338
+msgid "W:"
+msgstr "ع:"
+
+#: frontend/pisa_main_window.cc:1350
+msgid "H:"
+msgstr "ا:"
+
+#: frontend/pisa_main_window.cc:1375
+msgid "Scale"
+msgstr "التحجيم"
+
+#: frontend/pisa_main_window.cc:1399
+msgid "Focus"
+msgstr "البؤرة"
+
+#: frontend/pisa_main_window.cc:1429
+msgid "Options"
+msgstr "الخيارات"
+
+#: frontend/pisa_main_window.cc:1720
+msgid "Calibration"
+msgstr "المعايرة"
+
+#: frontend/pisa_main_window.cc:1731
+msgid "Cleaning"
+msgstr "التنظيف"
+
+#: frontend/pisa_progress_window.cc:62 frontend/pisa_view_manager.cc:774
+msgid " Finish "
+msgstr " أنهِ "
+
+#: frontend/pisa_progress_window.cc:171
+msgid "Scanner is warming up. Please wait..."
+msgstr "تُسخّن الماسحة. انتظر من فضلك..."
+
+#: frontend/pisa_progress_window.cc:174
+msgid "Pre-scanning in Progress"
+msgstr "يجري الإعداد للمسح"
+
+#: frontend/pisa_progress_window.cc:177
+msgid "Scanning in Progress"
+msgstr "يجري المسح"
+
+#: frontend/pisa_progress_window.cc:180
+msgid ""
+"Starting a sequence of scans.\n"
+"Press to scanner's Start button to start each scan."
+msgstr ""
+"يبدأ سلسلة من المسح.\n"
+"انقر على زر بدء الماسدة لبدء كل مسحة."
+
+#: frontend/pisa_scan_selector.cc:103
+msgid "Scan Selector Dialog"
+msgstr "حوار اختيار المسح"
+
+#: frontend/pisa_scan_selector.cc:163
+msgid " Update "
+msgstr " حدّث "
+
+#: frontend/pisa_view_manager.cc:762
+msgid " Continue "
+msgstr " واصل "
+
+#~ msgid "Adjust"
+#~ msgstr "اضبط"
+
+#~ msgid "Match the direction of front and back images"
+#~ msgstr "طابق اتجاه صور المقدمة والخلفية"
diff --git a/po/boldquot.sed b/po/boldquot.sed
new file mode 100644
index 0000000..4b937aa
--- /dev/null
+++ b/po/boldquot.sed
@@ -0,0 +1,10 @@
+s/"\([^"]*\)"/“\1”/g
+s/`\([^`']*\)'/‘\1’/g
+s/ '\([^`']*\)' / ‘\1’ /g
+s/ '\([^`']*\)'$/ ‘\1’/g
+s/^'\([^`']*\)' /‘\1’ /g
+s/“”/""/g
+s/“/“/g
+s/”/”/g
+s/‘/‘/g
+s/’/’/g
diff --git a/po/de.gmo b/po/de.gmo
new file mode 100644
index 0000000..5371f53
--- /dev/null
+++ b/po/de.gmo
Binary files differ
diff --git a/po/de.po b/po/de.po
new file mode 100644
index 0000000..33ebc71
--- /dev/null
+++ b/po/de.po
@@ -0,0 +1,469 @@
+# de.po -- German translations for `iscan' messages
+# Copyright (C) 2003, 2012 SEIKO EPSON CORPORATION
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: iscan 1.9.0\n"
+"Report-Msgid-Bugs-To: linux-printer@epson.jp\n"
+"POT-Creation-Date: 2019-08-21 08:06+0900\n"
+"PO-Revision-Date: 2009-09-11 10:38+0900\n"
+"Last-Translator: SEIKO EPSON CORPORATION\n"
+"Language-Team: German\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: \n"
+
+#: frontend/file-selector.cc:465
+msgid "Overwrite"
+msgstr "Überschreiben"
+
+#: frontend/file-selector.cc:465
+msgid "Cancel"
+msgstr "Abbrechen"
+
+#: frontend/file-selector.cc:746
+msgid "Save Options"
+msgstr "Einstellungen"
+
+#: frontend/file-selector.cc:765
+msgid "Create file with all pages"
+msgstr "Datei mit allen Seiten erst."
+
+#: frontend/file-selector.cc:768
+msgid "Binding Position (for Double-Sided Scanning)"
+msgstr "Bindungsposition (f. doppelseitiges Scannen)"
+
+#: frontend/file-selector.cc:769
+msgid "Left"
+msgstr "Links"
+
+#: frontend/file-selector.cc:771
+msgid "Top"
+msgstr "Oben"
+
+#: frontend/file-selector.cc:776
+msgid "Determine File Type:"
+msgstr "Dateityp bestimmen:"
+
+#: frontend/file-selector.cc:835
+msgid "Start filing at:"
+msgstr "Dateinamenen beginnen mit"
+
+#: frontend/file-selector.cc:858
+msgid "Number of digits:"
+msgstr "Anzahl Zeichen"
+
+#: frontend/pisa_aleart_dialog.cc:73 frontend/pisa_configuration.cc:204
+#: frontend/pisa_scan_selector.cc:199
+msgid " OK "
+msgstr " OK "
+
+#: frontend/pisa_configuration.cc:113 frontend/pisa_main_window.cc:849
+msgid "Configuration"
+msgstr "Konfiguration"
+
+#: frontend/pisa_configuration.cc:163
+msgid "Print Command"
+msgstr "Druckbefehl"
+
+#: frontend/pisa_configuration.cc:186
+msgid ""
+"In order to print, your print system must be able to handle the PNG file "
+"format directly. CUPS or Photo Image Print System (versions 1.3.1 or later) "
+"do this by default."
+msgstr ""
+
+#: frontend/pisa_configuration.cc:214 frontend/pisa_progress_window.cc:61
+#: frontend/pisa_scan_selector.cc:213 frontend/pisa_view_manager.cc:762
+msgid " Cancel "
+msgstr " Abbrechen "
+
+#: frontend/pisa_error.cc:85
+msgid "Operation completed succesfully."
+msgstr ""
+
+#: frontend/pisa_error.cc:87
+msgid "Operation is not supported."
+msgstr ""
+
+#: frontend/pisa_error.cc:89
+msgid "Operation was cancelled."
+msgstr ""
+
+#: frontend/pisa_error.cc:91
+msgid "Device is busy---retry later."
+msgstr ""
+
+#: frontend/pisa_error.cc:93
+msgid "Data or argument is invalid."
+msgstr ""
+
+#: frontend/pisa_error.cc:95
+msgid "No more data available (end-of-file)."
+msgstr ""
+
+#: frontend/pisa_error.cc:97
+msgid ""
+"A paper jam occured. Open the Automatic Document Feeder and remove any "
+"paper."
+msgstr ""
+"Ein Papierstau ist aufgetreten.\n"
+"Öffnen Sie den automatischen Vorlageneinzug, und entfernen Sie das gestaute "
+"Papier."
+
+#: frontend/pisa_error.cc:100
+msgid "Please load the document(s) into the Automatic Document Feeder."
+msgstr "Legen Sie die Vorlage(n) in den automatischen Vorlageneinzug ein."
+
+#: frontend/pisa_error.cc:103
+msgid ""
+"The automatic document feeder or scanner unit is open.\n"
+"Please close it."
+msgstr ""
+"Der automatische Vorlageneinzug oder die Scanner-Einheit ist offen. "
+"Schließen Sie den Einzug bzw. die Einheit."
+
+#: frontend/pisa_error.cc:106
+msgid "Error during device I/O."
+msgstr ""
+
+#: frontend/pisa_error.cc:108
+msgid "Out of memory."
+msgstr ""
+
+#: frontend/pisa_error.cc:110
+msgid "Access to resource has been denied."
+msgstr ""
+
+#: frontend/pisa_error.cc:113
+msgid "There is not enough disk space for operation"
+msgstr "Es ist nicht genug Festplattenspeicher für den Vorgang verfügbar"
+
+#: frontend/pisa_error.cc:116
+msgid ""
+"Could not send command to scanner.\n"
+"Check the scanner's status."
+msgstr ""
+"Es konnte kein Befehl an den Scanner gesendet werden. Überprüfen Sie den "
+"Scanner-Status."
+
+#: frontend/pisa_error.cc:120
+msgid "Scanner model not supported"
+msgstr "Scanner-Modell wird nicht"
+
+#: frontend/pisa_error.cc:123
+msgid ""
+"Selected area is too large for this resolution.\n"
+"Reduce the selected area or resolution."
+msgstr ""
+"Der ausgewählte Bereich ist zu groß für diese Auflösung.\n"
+"Stellen Sie eine niedrigere Auflösung oder Skalierung ein, oder verkleinern "
+"Sie den ausgewählten Bereich."
+
+#: frontend/pisa_error.cc:127 frontend/pisa_error.cc:130
+msgid "Could not create file"
+msgstr "Die Datei konnte nicht erstellt werden"
+
+#: frontend/pisa_error.cc:133
+msgid ""
+"A file with the same name already exists.\n"
+"Click \"Overwrite\" to replace the file or \"Cancel\" if you want to use "
+"another file name."
+msgstr ""
+"Eine Datei mit dem gleichen Namen ist bereits vorhanden.\n"
+"Klicken Sie auf „Überschreiben“, um die Datei zu ersetzen oder auf "
+"„Abbrechen“, falls Sie einen anderen Dateinamen verwenden wollen."
+
+#: frontend/pisa_error.cc:138
+msgid ""
+"The Image Type setting you selected cannot be used with this resolution.\n"
+"Reduce the Resolution or Scale setting."
+msgstr ""
+"Die Einstellung kann in dieser Auflösung nicht verwendet werden Bitte die "
+"Auflösung oder den Zoomfaktor verringern"
+
+#: frontend/pisa_error.cc:143
+msgid "Tray cover is closed. Please open the tray cover and then scan again."
+msgstr ""
+"Die Abdeckung ist geschlossen. Öffnen Sie die Abdeckung und versuchen Sie es "
+"erneut."
+
+#: frontend/pisa_error.cc:147
+msgid ""
+"A multi page feed occurred in the auto document feeder.\n"
+"Open the cover, remove the documents, and then try again. If documents "
+"remain on the tray, remove them and then reload them."
+msgstr ""
+"Es wurden mehrere Vorlagen gleichzeitig im automatischem Vorlageneinzug "
+"eingezogen.\n"
+"Öffnen Sie die Abdeckung, entfernen Sie die Dokumente und starten Sie den "
+"Vorgang erneut. Entfernen Sie vorhandene Vorlagen aus dem Fach und legen Sie "
+"sie erneut ein."
+
+#: frontend/pisa_error.cc:156
+msgid "Unexpected error occurred"
+msgstr "Kein Fehler"
+
+#: frontend/pisa_gamma_correction.cc:482
+msgid " Reset "
+msgstr " Zurücksetzen "
+
+#: frontend/pisa_image_controls.cc:253
+msgid "Gamma:"
+msgstr "Gamma:"
+
+#: frontend/pisa_image_controls.cc:268
+msgid "Highlight:"
+msgstr "Lichter:"
+
+#: frontend/pisa_image_controls.cc:283
+msgid "Shadow:"
+msgstr "Schatten:"
+
+#: frontend/pisa_image_controls.cc:298
+msgid "Threshold:"
+msgstr "Schwellenwert:"
+
+#: frontend/pisa_image_controls.cc:331
+msgid "Brightness:"
+msgstr "Helligkeit:"
+
+#: frontend/pisa_image_controls.cc:354
+msgid "Contrast:"
+msgstr "Kontrast:"
+
+#: frontend/pisa_main_window.cc:90
+msgid "File"
+msgstr "Datei"
+
+#: frontend/pisa_main_window.cc:91
+msgid "Printer"
+msgstr "Drucker"
+
+#: frontend/pisa_main_window.cc:104
+msgid "Flatbed"
+msgstr "Flachbett"
+
+#: frontend/pisa_main_window.cc:105
+msgid "TPU - Negative film"
+msgstr "TPU für Neg.Film"
+
+#: frontend/pisa_main_window.cc:106
+msgid "TPU - Positive film"
+msgstr "TPU für Pos.Film"
+
+#: frontend/pisa_main_window.cc:107
+msgid "ADF - Single-sided"
+msgstr "ADF-Einseitig"
+
+#: frontend/pisa_main_window.cc:108
+msgid "ADF - Double-sided"
+msgstr "ADF-Doppelseitig"
+
+#: frontend/pisa_main_window.cc:121
+msgid "Color Photo"
+msgstr "Farbfoto"
+
+#: frontend/pisa_main_window.cc:122
+msgid "Color Document"
+msgstr "Farbvorlage"
+
+#: frontend/pisa_main_window.cc:123
+msgid "Black & White Photo"
+msgstr "Schwarz-Weißfoto"
+
+#: frontend/pisa_main_window.cc:124
+msgid "Black & White Document"
+msgstr "Schwarz-Weiss Vorlage"
+
+#: frontend/pisa_main_window.cc:125
+msgid "Line Art"
+msgstr "Strichzeichnungen"
+
+#: frontend/pisa_main_window.cc:169
+msgid "inches"
+msgstr "Zoll"
+
+#: frontend/pisa_main_window.cc:170
+msgid "pixels"
+msgstr "Pixel"
+
+#: frontend/pisa_main_window.cc:171
+msgid "cm"
+msgstr "cm"
+
+#: frontend/pisa_main_window.cc:192
+msgid "Off"
+msgstr "Aus"
+
+#: frontend/pisa_main_window.cc:193
+msgid "Standard"
+msgstr "Standard"
+
+#: frontend/pisa_main_window.cc:194
+msgid "Thin"
+msgstr "Dünn"
+
+#: frontend/pisa_main_window.cc:299
+msgid "Calibration is failed."
+msgstr "Kalibrierung fehlgeschlagen."
+
+#: frontend/pisa_main_window.cc:325
+msgid "Cleaning is failed."
+msgstr "Reinigung fehlgeschlagen."
+
+#: frontend/pisa_main_window.cc:831
+msgid "Scan to File"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:837
+msgid "Scan"
+msgstr "Scannen"
+
+#: frontend/pisa_main_window.cc:903
+msgid "Close"
+msgstr "Schließen"
+
+#: frontend/pisa_main_window.cc:922
+msgid "Preview"
+msgstr "Vorschau"
+
+#: frontend/pisa_main_window.cc:923
+msgid "Zoom"
+msgstr "Zoom"
+
+#: frontend/pisa_main_window.cc:924
+msgid "Auto Exposure"
+msgstr "Automatische Belichtung"
+
+#: frontend/pisa_main_window.cc:927
+msgid "enable Start button"
+msgstr "Starttaste aktivieren"
+
+#: frontend/pisa_main_window.cc:928
+msgid "Speed priority scanning"
+msgstr "Geschwindigkeitsprioritäts-Scan"
+
+#: frontend/pisa_main_window.cc:930
+msgid "Document Size - Auto Detect"
+msgstr "Vorlagengröße - Automatische Erkennung"
+
+#: frontend/pisa_main_window.cc:932
+msgid "Correct Document Skew"
+msgstr "Schräg-Korrektur der Vorlage"
+
+#: frontend/pisa_main_window.cc:937
+msgid "Unsharp mask"
+msgstr "Unscharfe Maske"
+
+#: frontend/pisa_main_window.cc:941
+msgid "Detect Double Feed (Paper Thickness)"
+msgstr "Doppeleinzugsfehler (Papierstärke)"
+
+#: frontend/pisa_main_window.cc:1027
+msgid "Scan to Print"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1073
+msgid "Destination:"
+msgstr "Ziel:"
+
+#: frontend/pisa_main_window.cc:1152 frontend/pisa_scan_selector.cc:142
+msgid "Scanner:"
+msgstr "Scanner:"
+
+#: frontend/pisa_main_window.cc:1175
+msgid "Document"
+msgstr "Vorlage"
+
+#: frontend/pisa_main_window.cc:1186
+msgid "Image Controls"
+msgstr "Bildsteuerung"
+
+#: frontend/pisa_main_window.cc:1195
+msgid "Tone Correction"
+msgstr "Tonwertkorrektur"
+
+#: frontend/pisa_main_window.cc:1233
+msgid "Document Source:"
+msgstr "Dokumentenquelle:"
+
+#: frontend/pisa_main_window.cc:1266
+msgid "Image Type:"
+msgstr "Bildtyp:"
+
+#: frontend/pisa_main_window.cc:1278
+msgid "Resolution:"
+msgstr "Auflösung:"
+
+#: frontend/pisa_main_window.cc:1327
+msgid "Target"
+msgstr "Zielgröße"
+
+#: frontend/pisa_main_window.cc:1338
+msgid "W:"
+msgstr "B:"
+
+#: frontend/pisa_main_window.cc:1350
+msgid "H:"
+msgstr "H:"
+
+#: frontend/pisa_main_window.cc:1375
+msgid "Scale"
+msgstr "Zoom"
+
+#: frontend/pisa_main_window.cc:1399
+msgid "Focus"
+msgstr "Fokus"
+
+#: frontend/pisa_main_window.cc:1429
+msgid "Options"
+msgstr "Optionen"
+
+#: frontend/pisa_main_window.cc:1720
+msgid "Calibration"
+msgstr "Kalibrierung"
+
+#: frontend/pisa_main_window.cc:1731
+msgid "Cleaning"
+msgstr "Reinigung"
+
+#: frontend/pisa_progress_window.cc:62 frontend/pisa_view_manager.cc:774
+msgid " Finish "
+msgstr " Fertig stellen "
+
+#: frontend/pisa_progress_window.cc:171
+msgid "Scanner is warming up. Please wait..."
+msgstr "Der Scanner befindet sich in der Aufwärmphase. Bitte warten..."
+
+#: frontend/pisa_progress_window.cc:174
+msgid "Pre-scanning in Progress"
+msgstr "Vorschau-Scan läuft"
+
+#: frontend/pisa_progress_window.cc:177
+msgid "Scanning in Progress"
+msgstr "Scanvorgang läuft"
+
+#: frontend/pisa_progress_window.cc:180
+msgid ""
+"Starting a sequence of scans.\n"
+"Press to scanner's Start button to start each scan."
+msgstr ""
+"Scan-Folge starten. Drücken Sie die Starttaste am Scanner, um einen Scan-"
+"Vorgang zu starten."
+
+#: frontend/pisa_scan_selector.cc:103
+msgid "Scan Selector Dialog"
+msgstr ""
+
+#: frontend/pisa_scan_selector.cc:163
+msgid " Update "
+msgstr " Aktualisieren "
+
+#: frontend/pisa_view_manager.cc:762
+msgid " Continue "
+msgstr " Fortfahren "
+
+#~ msgid "Adjust"
+#~ msgstr "Einstellungen"
diff --git a/po/en@boldquot.gmo b/po/en@boldquot.gmo
new file mode 100644
index 0000000..95b5f32
--- /dev/null
+++ b/po/en@boldquot.gmo
Binary files differ
diff --git a/po/en@boldquot.header b/po/en@boldquot.header
new file mode 100644
index 0000000..fedb6a0
--- /dev/null
+++ b/po/en@boldquot.header
@@ -0,0 +1,25 @@
+# All this catalog "translates" are quotation characters.
+# The msgids must be ASCII and therefore cannot contain real quotation
+# characters, only substitutes like grave accent (0x60), apostrophe (0x27)
+# and double quote (0x22). These substitutes look strange; see
+# http://www.cl.cam.ac.uk/~mgk25/ucs/quotes.html
+#
+# This catalog translates grave accent (0x60) and apostrophe (0x27) to
+# left single quotation mark (U+2018) and right single quotation mark (U+2019).
+# It also translates pairs of apostrophe (0x27) to
+# left single quotation mark (U+2018) and right single quotation mark (U+2019)
+# and pairs of quotation mark (0x22) to
+# left double quotation mark (U+201C) and right double quotation mark (U+201D).
+#
+# When output to an UTF-8 terminal, the quotation characters appear perfectly.
+# When output to an ISO-8859-1 terminal, the single quotation marks are
+# transliterated to apostrophes (by iconv in glibc 2.2 or newer) or to
+# grave/acute accent (by libiconv), and the double quotation marks are
+# transliterated to 0x22.
+# When output to an ASCII terminal, the single quotation marks are
+# transliterated to apostrophes, and the double quotation marks are
+# transliterated to 0x22.
+#
+# This catalog furthermore displays the text between the quotation marks in
+# bold face, assuming the VT100/XTerm escape sequences.
+#
diff --git a/po/en@boldquot.po b/po/en@boldquot.po
new file mode 100644
index 0000000..b425862
--- /dev/null
+++ b/po/en@boldquot.po
@@ -0,0 +1,490 @@
+# English translations for Image Scan! for Linux package.
+# Copyright (C) 2019 SEIKO EPSON CORPORATION
+# This file is distributed under the same license as the Image Scan! for Linux package.
+# Automatically generated, 2019.
+#
+# All this catalog "translates" are quotation characters.
+# The msgids must be ASCII and therefore cannot contain real quotation
+# characters, only substitutes like grave accent (0x60), apostrophe (0x27)
+# and double quote (0x22). These substitutes look strange; see
+# http://www.cl.cam.ac.uk/~mgk25/ucs/quotes.html
+#
+# This catalog translates grave accent (0x60) and apostrophe (0x27) to
+# left single quotation mark (U+2018) and right single quotation mark (U+2019).
+# It also translates pairs of apostrophe (0x27) to
+# left single quotation mark (U+2018) and right single quotation mark (U+2019)
+# and pairs of quotation mark (0x22) to
+# left double quotation mark (U+201C) and right double quotation mark (U+201D).
+#
+# When output to an UTF-8 terminal, the quotation characters appear perfectly.
+# When output to an ISO-8859-1 terminal, the single quotation marks are
+# transliterated to apostrophes (by iconv in glibc 2.2 or newer) or to
+# grave/acute accent (by libiconv), and the double quotation marks are
+# transliterated to 0x22.
+# When output to an ASCII terminal, the single quotation marks are
+# transliterated to apostrophes, and the double quotation marks are
+# transliterated to 0x22.
+#
+# This catalog furthermore displays the text between the quotation marks in
+# bold face, assuming the VT100/XTerm escape sequences.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Image Scan! for Linux 2.30.4\n"
+"Report-Msgid-Bugs-To: linux-printer@epson.jp\n"
+"POT-Creation-Date: 2019-08-21 08:06+0900\n"
+"PO-Revision-Date: 2019-08-21 08:06+0900\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: frontend/file-selector.cc:465
+msgid "Overwrite"
+msgstr "Overwrite"
+
+#: frontend/file-selector.cc:465
+msgid "Cancel"
+msgstr "Cancel"
+
+#: frontend/file-selector.cc:746
+msgid "Save Options"
+msgstr "Save Options"
+
+#: frontend/file-selector.cc:765
+msgid "Create file with all pages"
+msgstr "Create file with all pages"
+
+#: frontend/file-selector.cc:768
+msgid "Binding Position (for Double-Sided Scanning)"
+msgstr "Binding Position (for Double-Sided Scanning)"
+
+#: frontend/file-selector.cc:769
+msgid "Left"
+msgstr "Left"
+
+#: frontend/file-selector.cc:771
+msgid "Top"
+msgstr "Top"
+
+#: frontend/file-selector.cc:776
+msgid "Determine File Type:"
+msgstr "Determine File Type:"
+
+#: frontend/file-selector.cc:835
+msgid "Start filing at:"
+msgstr "Start filing at:"
+
+#: frontend/file-selector.cc:858
+msgid "Number of digits:"
+msgstr "Number of digits:"
+
+#: frontend/pisa_aleart_dialog.cc:73 frontend/pisa_configuration.cc:204
+#: frontend/pisa_scan_selector.cc:199
+msgid " OK "
+msgstr " OK "
+
+#: frontend/pisa_configuration.cc:113 frontend/pisa_main_window.cc:849
+msgid "Configuration"
+msgstr "Configuration"
+
+#: frontend/pisa_configuration.cc:163
+msgid "Print Command"
+msgstr "Print Command"
+
+#: frontend/pisa_configuration.cc:186
+msgid ""
+"In order to print, your print system must be able to handle the PNG file "
+"format directly. CUPS or Photo Image Print System (versions 1.3.1 or later) "
+"do this by default."
+msgstr ""
+"In order to print, your print system must be able to handle the PNG file "
+"format directly. CUPS or Photo Image Print System (versions 1.3.1 or later) "
+"do this by default."
+
+#: frontend/pisa_configuration.cc:214 frontend/pisa_progress_window.cc:61
+#: frontend/pisa_scan_selector.cc:213 frontend/pisa_view_manager.cc:762
+msgid " Cancel "
+msgstr " Cancel "
+
+#: frontend/pisa_error.cc:85
+msgid "Operation completed succesfully."
+msgstr "Operation completed succesfully."
+
+#: frontend/pisa_error.cc:87
+msgid "Operation is not supported."
+msgstr "Operation is not supported."
+
+#: frontend/pisa_error.cc:89
+msgid "Operation was cancelled."
+msgstr "Operation was cancelled."
+
+#: frontend/pisa_error.cc:91
+msgid "Device is busy---retry later."
+msgstr "Device is busy---retry later."
+
+#: frontend/pisa_error.cc:93
+msgid "Data or argument is invalid."
+msgstr "Data or argument is invalid."
+
+#: frontend/pisa_error.cc:95
+msgid "No more data available (end-of-file)."
+msgstr "No more data available (end-of-file)."
+
+#: frontend/pisa_error.cc:97
+msgid ""
+"A paper jam occured. Open the Automatic Document Feeder and remove any "
+"paper."
+msgstr ""
+"A paper jam occured. Open the Automatic Document Feeder and remove any "
+"paper."
+
+#: frontend/pisa_error.cc:100
+msgid "Please load the document(s) into the Automatic Document Feeder."
+msgstr "Please load the document(s) into the Automatic Document Feeder."
+
+#: frontend/pisa_error.cc:103
+msgid ""
+"The automatic document feeder or scanner unit is open.\n"
+"Please close it."
+msgstr ""
+"The automatic document feeder or scanner unit is open.\n"
+"Please close it."
+
+#: frontend/pisa_error.cc:106
+msgid "Error during device I/O."
+msgstr "Error during device I/O."
+
+#: frontend/pisa_error.cc:108
+msgid "Out of memory."
+msgstr "Out of memory."
+
+#: frontend/pisa_error.cc:110
+msgid "Access to resource has been denied."
+msgstr "Access to resource has been denied."
+
+#: frontend/pisa_error.cc:113
+msgid "There is not enough disk space for operation"
+msgstr "There is not enough disk space for operation"
+
+#: frontend/pisa_error.cc:116
+msgid ""
+"Could not send command to scanner.\n"
+"Check the scanner's status."
+msgstr ""
+"Could not send command to scanner.\n"
+"Check the scanner's status."
+
+#: frontend/pisa_error.cc:120
+msgid "Scanner model not supported"
+msgstr "Scanner model not supported"
+
+#: frontend/pisa_error.cc:123
+msgid ""
+"Selected area is too large for this resolution.\n"
+"Reduce the selected area or resolution."
+msgstr ""
+"Selected area is too large for this resolution.\n"
+"Reduce the selected area or resolution."
+
+#: frontend/pisa_error.cc:127 frontend/pisa_error.cc:130
+msgid "Could not create file"
+msgstr "Could not create file"
+
+#: frontend/pisa_error.cc:133
+msgid ""
+"A file with the same name already exists.\n"
+"Click \"Overwrite\" to replace the file or \"Cancel\" if you want to use "
+"another file name."
+msgstr ""
+"A file with the same name already exists.\n"
+"Click “Overwrite” to replace the file or “Cancel” if you want to "
+"use another file name."
+
+#: frontend/pisa_error.cc:138
+msgid ""
+"The Image Type setting you selected cannot be used with this resolution.\n"
+"Reduce the Resolution or Scale setting."
+msgstr ""
+"The Image Type setting you selected cannot be used with this resolution.\n"
+"Reduce the Resolution or Scale setting."
+
+#: frontend/pisa_error.cc:143
+msgid "Tray cover is closed. Please open the tray cover and then scan again."
+msgstr "Tray cover is closed. Please open the tray cover and then scan again."
+
+#: frontend/pisa_error.cc:147
+msgid ""
+"A multi page feed occurred in the auto document feeder.\n"
+"Open the cover, remove the documents, and then try again. If documents "
+"remain on the tray, remove them and then reload them."
+msgstr ""
+"A multi page feed occurred in the auto document feeder.\n"
+"Open the cover, remove the documents, and then try again. If documents "
+"remain on the tray, remove them and then reload them."
+
+#: frontend/pisa_error.cc:156
+msgid "Unexpected error occurred"
+msgstr "Unexpected error occurred"
+
+#: frontend/pisa_gamma_correction.cc:482
+msgid " Reset "
+msgstr " Reset "
+
+#: frontend/pisa_image_controls.cc:253
+msgid "Gamma:"
+msgstr "Gamma:"
+
+#: frontend/pisa_image_controls.cc:268
+msgid "Highlight:"
+msgstr "Highlight:"
+
+#: frontend/pisa_image_controls.cc:283
+msgid "Shadow:"
+msgstr "Shadow:"
+
+#: frontend/pisa_image_controls.cc:298
+msgid "Threshold:"
+msgstr "Threshold:"
+
+#: frontend/pisa_image_controls.cc:331
+msgid "Brightness:"
+msgstr "Brightness:"
+
+#: frontend/pisa_image_controls.cc:354
+msgid "Contrast:"
+msgstr "Contrast:"
+
+#: frontend/pisa_main_window.cc:90
+msgid "File"
+msgstr "File"
+
+#: frontend/pisa_main_window.cc:91
+msgid "Printer"
+msgstr "Printer"
+
+#: frontend/pisa_main_window.cc:104
+msgid "Flatbed"
+msgstr "Flatbed"
+
+#: frontend/pisa_main_window.cc:105
+msgid "TPU - Negative film"
+msgstr "TPU - Negative film"
+
+#: frontend/pisa_main_window.cc:106
+msgid "TPU - Positive film"
+msgstr "TPU - Positive film"
+
+#: frontend/pisa_main_window.cc:107
+msgid "ADF - Single-sided"
+msgstr "ADF - Single-sided"
+
+#: frontend/pisa_main_window.cc:108
+msgid "ADF - Double-sided"
+msgstr "ADF - Double-sided"
+
+#: frontend/pisa_main_window.cc:121
+msgid "Color Photo"
+msgstr "Color Photo"
+
+#: frontend/pisa_main_window.cc:122
+msgid "Color Document"
+msgstr "Color Document"
+
+#: frontend/pisa_main_window.cc:123
+msgid "Black & White Photo"
+msgstr "Black & White Photo"
+
+#: frontend/pisa_main_window.cc:124
+msgid "Black & White Document"
+msgstr "Black & White Document"
+
+#: frontend/pisa_main_window.cc:125
+msgid "Line Art"
+msgstr "Line Art"
+
+#: frontend/pisa_main_window.cc:169
+msgid "inches"
+msgstr "inches"
+
+#: frontend/pisa_main_window.cc:170
+msgid "pixels"
+msgstr "pixels"
+
+#: frontend/pisa_main_window.cc:171
+msgid "cm"
+msgstr "cm"
+
+#: frontend/pisa_main_window.cc:192
+msgid "Off"
+msgstr "Off"
+
+#: frontend/pisa_main_window.cc:193
+msgid "Standard"
+msgstr "Standard"
+
+#: frontend/pisa_main_window.cc:194
+msgid "Thin"
+msgstr "Thin"
+
+#: frontend/pisa_main_window.cc:299
+msgid "Calibration is failed."
+msgstr "Calibration is failed."
+
+#: frontend/pisa_main_window.cc:325
+msgid "Cleaning is failed."
+msgstr "Cleaning is failed."
+
+#: frontend/pisa_main_window.cc:831
+msgid "Scan to File"
+msgstr "Scan to File"
+
+#: frontend/pisa_main_window.cc:837
+msgid "Scan"
+msgstr "Scan"
+
+#: frontend/pisa_main_window.cc:903
+msgid "Close"
+msgstr "Close"
+
+#: frontend/pisa_main_window.cc:922
+msgid "Preview"
+msgstr "Preview"
+
+#: frontend/pisa_main_window.cc:923
+msgid "Zoom"
+msgstr "Zoom"
+
+#: frontend/pisa_main_window.cc:924
+msgid "Auto Exposure"
+msgstr "Auto Exposure"
+
+#: frontend/pisa_main_window.cc:927
+msgid "enable Start button"
+msgstr "enable Start button"
+
+#: frontend/pisa_main_window.cc:928
+msgid "Speed priority scanning"
+msgstr "Speed priority scanning"
+
+#: frontend/pisa_main_window.cc:930
+msgid "Document Size - Auto Detect"
+msgstr "Document Size - Auto Detect"
+
+#: frontend/pisa_main_window.cc:932
+msgid "Correct Document Skew"
+msgstr "Correct Document Skew"
+
+#: frontend/pisa_main_window.cc:937
+msgid "Unsharp mask"
+msgstr "Unsharp mask"
+
+#: frontend/pisa_main_window.cc:941
+msgid "Detect Double Feed (Paper Thickness)"
+msgstr "Detect Double Feed (Paper Thickness)"
+
+#: frontend/pisa_main_window.cc:1027
+msgid "Scan to Print"
+msgstr "Scan to Print"
+
+#: frontend/pisa_main_window.cc:1073
+msgid "Destination:"
+msgstr "Destination:"
+
+#: frontend/pisa_main_window.cc:1152 frontend/pisa_scan_selector.cc:142
+msgid "Scanner:"
+msgstr "Scanner:"
+
+#: frontend/pisa_main_window.cc:1175
+msgid "Document"
+msgstr "Document"
+
+#: frontend/pisa_main_window.cc:1186
+msgid "Image Controls"
+msgstr "Image Controls"
+
+#: frontend/pisa_main_window.cc:1195
+msgid "Tone Correction"
+msgstr "Tone Correction"
+
+#: frontend/pisa_main_window.cc:1233
+msgid "Document Source:"
+msgstr "Document Source:"
+
+#: frontend/pisa_main_window.cc:1266
+msgid "Image Type:"
+msgstr "Image Type:"
+
+#: frontend/pisa_main_window.cc:1278
+msgid "Resolution:"
+msgstr "Resolution:"
+
+#: frontend/pisa_main_window.cc:1327
+msgid "Target"
+msgstr "Target"
+
+#: frontend/pisa_main_window.cc:1338
+msgid "W:"
+msgstr "W:"
+
+#: frontend/pisa_main_window.cc:1350
+msgid "H:"
+msgstr "H:"
+
+#: frontend/pisa_main_window.cc:1375
+msgid "Scale"
+msgstr "Scale"
+
+#: frontend/pisa_main_window.cc:1399
+msgid "Focus"
+msgstr "Focus"
+
+#: frontend/pisa_main_window.cc:1429
+msgid "Options"
+msgstr "Options"
+
+#: frontend/pisa_main_window.cc:1720
+msgid "Calibration"
+msgstr "Calibration"
+
+#: frontend/pisa_main_window.cc:1731
+msgid "Cleaning"
+msgstr "Cleaning"
+
+#: frontend/pisa_progress_window.cc:62 frontend/pisa_view_manager.cc:774
+msgid " Finish "
+msgstr " Finish "
+
+#: frontend/pisa_progress_window.cc:171
+msgid "Scanner is warming up. Please wait..."
+msgstr "Scanner is warming up. Please wait..."
+
+#: frontend/pisa_progress_window.cc:174
+msgid "Pre-scanning in Progress"
+msgstr "Pre-scanning in Progress"
+
+#: frontend/pisa_progress_window.cc:177
+msgid "Scanning in Progress"
+msgstr "Scanning in Progress"
+
+#: frontend/pisa_progress_window.cc:180
+msgid ""
+"Starting a sequence of scans.\n"
+"Press to scanner's Start button to start each scan."
+msgstr ""
+"Starting a sequence of scans.\n"
+"Press to scanner's Start button to start each scan."
+
+#: frontend/pisa_scan_selector.cc:103
+msgid "Scan Selector Dialog"
+msgstr "Scan Selector Dialog"
+
+#: frontend/pisa_scan_selector.cc:163
+msgid " Update "
+msgstr " Update "
+
+#: frontend/pisa_view_manager.cc:762
+msgid " Continue "
+msgstr " Continue "
diff --git a/po/en@quot.gmo b/po/en@quot.gmo
new file mode 100644
index 0000000..8b21611
--- /dev/null
+++ b/po/en@quot.gmo
Binary files differ
diff --git a/po/en@quot.header b/po/en@quot.header
new file mode 100644
index 0000000..a9647fc
--- /dev/null
+++ b/po/en@quot.header
@@ -0,0 +1,22 @@
+# All this catalog "translates" are quotation characters.
+# The msgids must be ASCII and therefore cannot contain real quotation
+# characters, only substitutes like grave accent (0x60), apostrophe (0x27)
+# and double quote (0x22). These substitutes look strange; see
+# http://www.cl.cam.ac.uk/~mgk25/ucs/quotes.html
+#
+# This catalog translates grave accent (0x60) and apostrophe (0x27) to
+# left single quotation mark (U+2018) and right single quotation mark (U+2019).
+# It also translates pairs of apostrophe (0x27) to
+# left single quotation mark (U+2018) and right single quotation mark (U+2019)
+# and pairs of quotation mark (0x22) to
+# left double quotation mark (U+201C) and right double quotation mark (U+201D).
+#
+# When output to an UTF-8 terminal, the quotation characters appear perfectly.
+# When output to an ISO-8859-1 terminal, the single quotation marks are
+# transliterated to apostrophes (by iconv in glibc 2.2 or newer) or to
+# grave/acute accent (by libiconv), and the double quotation marks are
+# transliterated to 0x22.
+# When output to an ASCII terminal, the single quotation marks are
+# transliterated to apostrophes, and the double quotation marks are
+# transliterated to 0x22.
+#
diff --git a/po/en@quot.po b/po/en@quot.po
new file mode 100644
index 0000000..d6879ec
--- /dev/null
+++ b/po/en@quot.po
@@ -0,0 +1,487 @@
+# English translations for Image Scan! for Linux package.
+# Copyright (C) 2019 SEIKO EPSON CORPORATION
+# This file is distributed under the same license as the Image Scan! for Linux package.
+# Automatically generated, 2019.
+#
+# All this catalog "translates" are quotation characters.
+# The msgids must be ASCII and therefore cannot contain real quotation
+# characters, only substitutes like grave accent (0x60), apostrophe (0x27)
+# and double quote (0x22). These substitutes look strange; see
+# http://www.cl.cam.ac.uk/~mgk25/ucs/quotes.html
+#
+# This catalog translates grave accent (0x60) and apostrophe (0x27) to
+# left single quotation mark (U+2018) and right single quotation mark (U+2019).
+# It also translates pairs of apostrophe (0x27) to
+# left single quotation mark (U+2018) and right single quotation mark (U+2019)
+# and pairs of quotation mark (0x22) to
+# left double quotation mark (U+201C) and right double quotation mark (U+201D).
+#
+# When output to an UTF-8 terminal, the quotation characters appear perfectly.
+# When output to an ISO-8859-1 terminal, the single quotation marks are
+# transliterated to apostrophes (by iconv in glibc 2.2 or newer) or to
+# grave/acute accent (by libiconv), and the double quotation marks are
+# transliterated to 0x22.
+# When output to an ASCII terminal, the single quotation marks are
+# transliterated to apostrophes, and the double quotation marks are
+# transliterated to 0x22.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Image Scan! for Linux 2.30.4\n"
+"Report-Msgid-Bugs-To: linux-printer@epson.jp\n"
+"POT-Creation-Date: 2019-08-21 08:06+0900\n"
+"PO-Revision-Date: 2019-08-21 08:06+0900\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: frontend/file-selector.cc:465
+msgid "Overwrite"
+msgstr "Overwrite"
+
+#: frontend/file-selector.cc:465
+msgid "Cancel"
+msgstr "Cancel"
+
+#: frontend/file-selector.cc:746
+msgid "Save Options"
+msgstr "Save Options"
+
+#: frontend/file-selector.cc:765
+msgid "Create file with all pages"
+msgstr "Create file with all pages"
+
+#: frontend/file-selector.cc:768
+msgid "Binding Position (for Double-Sided Scanning)"
+msgstr "Binding Position (for Double-Sided Scanning)"
+
+#: frontend/file-selector.cc:769
+msgid "Left"
+msgstr "Left"
+
+#: frontend/file-selector.cc:771
+msgid "Top"
+msgstr "Top"
+
+#: frontend/file-selector.cc:776
+msgid "Determine File Type:"
+msgstr "Determine File Type:"
+
+#: frontend/file-selector.cc:835
+msgid "Start filing at:"
+msgstr "Start filing at:"
+
+#: frontend/file-selector.cc:858
+msgid "Number of digits:"
+msgstr "Number of digits:"
+
+#: frontend/pisa_aleart_dialog.cc:73 frontend/pisa_configuration.cc:204
+#: frontend/pisa_scan_selector.cc:199
+msgid " OK "
+msgstr " OK "
+
+#: frontend/pisa_configuration.cc:113 frontend/pisa_main_window.cc:849
+msgid "Configuration"
+msgstr "Configuration"
+
+#: frontend/pisa_configuration.cc:163
+msgid "Print Command"
+msgstr "Print Command"
+
+#: frontend/pisa_configuration.cc:186
+msgid ""
+"In order to print, your print system must be able to handle the PNG file "
+"format directly. CUPS or Photo Image Print System (versions 1.3.1 or later) "
+"do this by default."
+msgstr ""
+"In order to print, your print system must be able to handle the PNG file "
+"format directly. CUPS or Photo Image Print System (versions 1.3.1 or later) "
+"do this by default."
+
+#: frontend/pisa_configuration.cc:214 frontend/pisa_progress_window.cc:61
+#: frontend/pisa_scan_selector.cc:213 frontend/pisa_view_manager.cc:762
+msgid " Cancel "
+msgstr " Cancel "
+
+#: frontend/pisa_error.cc:85
+msgid "Operation completed succesfully."
+msgstr "Operation completed succesfully."
+
+#: frontend/pisa_error.cc:87
+msgid "Operation is not supported."
+msgstr "Operation is not supported."
+
+#: frontend/pisa_error.cc:89
+msgid "Operation was cancelled."
+msgstr "Operation was cancelled."
+
+#: frontend/pisa_error.cc:91
+msgid "Device is busy---retry later."
+msgstr "Device is busy---retry later."
+
+#: frontend/pisa_error.cc:93
+msgid "Data or argument is invalid."
+msgstr "Data or argument is invalid."
+
+#: frontend/pisa_error.cc:95
+msgid "No more data available (end-of-file)."
+msgstr "No more data available (end-of-file)."
+
+#: frontend/pisa_error.cc:97
+msgid ""
+"A paper jam occured. Open the Automatic Document Feeder and remove any "
+"paper."
+msgstr ""
+"A paper jam occured. Open the Automatic Document Feeder and remove any "
+"paper."
+
+#: frontend/pisa_error.cc:100
+msgid "Please load the document(s) into the Automatic Document Feeder."
+msgstr "Please load the document(s) into the Automatic Document Feeder."
+
+#: frontend/pisa_error.cc:103
+msgid ""
+"The automatic document feeder or scanner unit is open.\n"
+"Please close it."
+msgstr ""
+"The automatic document feeder or scanner unit is open.\n"
+"Please close it."
+
+#: frontend/pisa_error.cc:106
+msgid "Error during device I/O."
+msgstr "Error during device I/O."
+
+#: frontend/pisa_error.cc:108
+msgid "Out of memory."
+msgstr "Out of memory."
+
+#: frontend/pisa_error.cc:110
+msgid "Access to resource has been denied."
+msgstr "Access to resource has been denied."
+
+#: frontend/pisa_error.cc:113
+msgid "There is not enough disk space for operation"
+msgstr "There is not enough disk space for operation"
+
+#: frontend/pisa_error.cc:116
+msgid ""
+"Could not send command to scanner.\n"
+"Check the scanner's status."
+msgstr ""
+"Could not send command to scanner.\n"
+"Check the scanner's status."
+
+#: frontend/pisa_error.cc:120
+msgid "Scanner model not supported"
+msgstr "Scanner model not supported"
+
+#: frontend/pisa_error.cc:123
+msgid ""
+"Selected area is too large for this resolution.\n"
+"Reduce the selected area or resolution."
+msgstr ""
+"Selected area is too large for this resolution.\n"
+"Reduce the selected area or resolution."
+
+#: frontend/pisa_error.cc:127 frontend/pisa_error.cc:130
+msgid "Could not create file"
+msgstr "Could not create file"
+
+#: frontend/pisa_error.cc:133
+msgid ""
+"A file with the same name already exists.\n"
+"Click \"Overwrite\" to replace the file or \"Cancel\" if you want to use "
+"another file name."
+msgstr ""
+"A file with the same name already exists.\n"
+"Click “Overwrite” to replace the file or “Cancel” if you want to use another "
+"file name."
+
+#: frontend/pisa_error.cc:138
+msgid ""
+"The Image Type setting you selected cannot be used with this resolution.\n"
+"Reduce the Resolution or Scale setting."
+msgstr ""
+"The Image Type setting you selected cannot be used with this resolution.\n"
+"Reduce the Resolution or Scale setting."
+
+#: frontend/pisa_error.cc:143
+msgid "Tray cover is closed. Please open the tray cover and then scan again."
+msgstr "Tray cover is closed. Please open the tray cover and then scan again."
+
+#: frontend/pisa_error.cc:147
+msgid ""
+"A multi page feed occurred in the auto document feeder.\n"
+"Open the cover, remove the documents, and then try again. If documents "
+"remain on the tray, remove them and then reload them."
+msgstr ""
+"A multi page feed occurred in the auto document feeder.\n"
+"Open the cover, remove the documents, and then try again. If documents "
+"remain on the tray, remove them and then reload them."
+
+#: frontend/pisa_error.cc:156
+msgid "Unexpected error occurred"
+msgstr "Unexpected error occurred"
+
+#: frontend/pisa_gamma_correction.cc:482
+msgid " Reset "
+msgstr " Reset "
+
+#: frontend/pisa_image_controls.cc:253
+msgid "Gamma:"
+msgstr "Gamma:"
+
+#: frontend/pisa_image_controls.cc:268
+msgid "Highlight:"
+msgstr "Highlight:"
+
+#: frontend/pisa_image_controls.cc:283
+msgid "Shadow:"
+msgstr "Shadow:"
+
+#: frontend/pisa_image_controls.cc:298
+msgid "Threshold:"
+msgstr "Threshold:"
+
+#: frontend/pisa_image_controls.cc:331
+msgid "Brightness:"
+msgstr "Brightness:"
+
+#: frontend/pisa_image_controls.cc:354
+msgid "Contrast:"
+msgstr "Contrast:"
+
+#: frontend/pisa_main_window.cc:90
+msgid "File"
+msgstr "File"
+
+#: frontend/pisa_main_window.cc:91
+msgid "Printer"
+msgstr "Printer"
+
+#: frontend/pisa_main_window.cc:104
+msgid "Flatbed"
+msgstr "Flatbed"
+
+#: frontend/pisa_main_window.cc:105
+msgid "TPU - Negative film"
+msgstr "TPU - Negative film"
+
+#: frontend/pisa_main_window.cc:106
+msgid "TPU - Positive film"
+msgstr "TPU - Positive film"
+
+#: frontend/pisa_main_window.cc:107
+msgid "ADF - Single-sided"
+msgstr "ADF - Single-sided"
+
+#: frontend/pisa_main_window.cc:108
+msgid "ADF - Double-sided"
+msgstr "ADF - Double-sided"
+
+#: frontend/pisa_main_window.cc:121
+msgid "Color Photo"
+msgstr "Color Photo"
+
+#: frontend/pisa_main_window.cc:122
+msgid "Color Document"
+msgstr "Color Document"
+
+#: frontend/pisa_main_window.cc:123
+msgid "Black & White Photo"
+msgstr "Black & White Photo"
+
+#: frontend/pisa_main_window.cc:124
+msgid "Black & White Document"
+msgstr "Black & White Document"
+
+#: frontend/pisa_main_window.cc:125
+msgid "Line Art"
+msgstr "Line Art"
+
+#: frontend/pisa_main_window.cc:169
+msgid "inches"
+msgstr "inches"
+
+#: frontend/pisa_main_window.cc:170
+msgid "pixels"
+msgstr "pixels"
+
+#: frontend/pisa_main_window.cc:171
+msgid "cm"
+msgstr "cm"
+
+#: frontend/pisa_main_window.cc:192
+msgid "Off"
+msgstr "Off"
+
+#: frontend/pisa_main_window.cc:193
+msgid "Standard"
+msgstr "Standard"
+
+#: frontend/pisa_main_window.cc:194
+msgid "Thin"
+msgstr "Thin"
+
+#: frontend/pisa_main_window.cc:299
+msgid "Calibration is failed."
+msgstr "Calibration is failed."
+
+#: frontend/pisa_main_window.cc:325
+msgid "Cleaning is failed."
+msgstr "Cleaning is failed."
+
+#: frontend/pisa_main_window.cc:831
+msgid "Scan to File"
+msgstr "Scan to File"
+
+#: frontend/pisa_main_window.cc:837
+msgid "Scan"
+msgstr "Scan"
+
+#: frontend/pisa_main_window.cc:903
+msgid "Close"
+msgstr "Close"
+
+#: frontend/pisa_main_window.cc:922
+msgid "Preview"
+msgstr "Preview"
+
+#: frontend/pisa_main_window.cc:923
+msgid "Zoom"
+msgstr "Zoom"
+
+#: frontend/pisa_main_window.cc:924
+msgid "Auto Exposure"
+msgstr "Auto Exposure"
+
+#: frontend/pisa_main_window.cc:927
+msgid "enable Start button"
+msgstr "enable Start button"
+
+#: frontend/pisa_main_window.cc:928
+msgid "Speed priority scanning"
+msgstr "Speed priority scanning"
+
+#: frontend/pisa_main_window.cc:930
+msgid "Document Size - Auto Detect"
+msgstr "Document Size - Auto Detect"
+
+#: frontend/pisa_main_window.cc:932
+msgid "Correct Document Skew"
+msgstr "Correct Document Skew"
+
+#: frontend/pisa_main_window.cc:937
+msgid "Unsharp mask"
+msgstr "Unsharp mask"
+
+#: frontend/pisa_main_window.cc:941
+msgid "Detect Double Feed (Paper Thickness)"
+msgstr "Detect Double Feed (Paper Thickness)"
+
+#: frontend/pisa_main_window.cc:1027
+msgid "Scan to Print"
+msgstr "Scan to Print"
+
+#: frontend/pisa_main_window.cc:1073
+msgid "Destination:"
+msgstr "Destination:"
+
+#: frontend/pisa_main_window.cc:1152 frontend/pisa_scan_selector.cc:142
+msgid "Scanner:"
+msgstr "Scanner:"
+
+#: frontend/pisa_main_window.cc:1175
+msgid "Document"
+msgstr "Document"
+
+#: frontend/pisa_main_window.cc:1186
+msgid "Image Controls"
+msgstr "Image Controls"
+
+#: frontend/pisa_main_window.cc:1195
+msgid "Tone Correction"
+msgstr "Tone Correction"
+
+#: frontend/pisa_main_window.cc:1233
+msgid "Document Source:"
+msgstr "Document Source:"
+
+#: frontend/pisa_main_window.cc:1266
+msgid "Image Type:"
+msgstr "Image Type:"
+
+#: frontend/pisa_main_window.cc:1278
+msgid "Resolution:"
+msgstr "Resolution:"
+
+#: frontend/pisa_main_window.cc:1327
+msgid "Target"
+msgstr "Target"
+
+#: frontend/pisa_main_window.cc:1338
+msgid "W:"
+msgstr "W:"
+
+#: frontend/pisa_main_window.cc:1350
+msgid "H:"
+msgstr "H:"
+
+#: frontend/pisa_main_window.cc:1375
+msgid "Scale"
+msgstr "Scale"
+
+#: frontend/pisa_main_window.cc:1399
+msgid "Focus"
+msgstr "Focus"
+
+#: frontend/pisa_main_window.cc:1429
+msgid "Options"
+msgstr "Options"
+
+#: frontend/pisa_main_window.cc:1720
+msgid "Calibration"
+msgstr "Calibration"
+
+#: frontend/pisa_main_window.cc:1731
+msgid "Cleaning"
+msgstr "Cleaning"
+
+#: frontend/pisa_progress_window.cc:62 frontend/pisa_view_manager.cc:774
+msgid " Finish "
+msgstr " Finish "
+
+#: frontend/pisa_progress_window.cc:171
+msgid "Scanner is warming up. Please wait..."
+msgstr "Scanner is warming up. Please wait..."
+
+#: frontend/pisa_progress_window.cc:174
+msgid "Pre-scanning in Progress"
+msgstr "Pre-scanning in Progress"
+
+#: frontend/pisa_progress_window.cc:177
+msgid "Scanning in Progress"
+msgstr "Scanning in Progress"
+
+#: frontend/pisa_progress_window.cc:180
+msgid ""
+"Starting a sequence of scans.\n"
+"Press to scanner's Start button to start each scan."
+msgstr ""
+"Starting a sequence of scans.\n"
+"Press to scanner's Start button to start each scan."
+
+#: frontend/pisa_scan_selector.cc:103
+msgid "Scan Selector Dialog"
+msgstr "Scan Selector Dialog"
+
+#: frontend/pisa_scan_selector.cc:163
+msgid " Update "
+msgstr " Update "
+
+#: frontend/pisa_view_manager.cc:762
+msgid " Continue "
+msgstr " Continue "
diff --git a/po/es.gmo b/po/es.gmo
new file mode 100644
index 0000000..10a114d
--- /dev/null
+++ b/po/es.gmo
Binary files differ
diff --git a/po/es.po b/po/es.po
new file mode 100644
index 0000000..8ca6f7f
--- /dev/null
+++ b/po/es.po
@@ -0,0 +1,468 @@
+# es.po -- Spanish translations for `iscan' messages
+# Copyright (C) 2003, 2012 SEIKO EPSON CORPORATION
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: iscan 1.9.0\n"
+"Report-Msgid-Bugs-To: linux-printer@epson.jp\n"
+"POT-Creation-Date: 2019-08-21 08:06+0900\n"
+"PO-Revision-Date: 2011-05-30 15:50+0900\n"
+"Last-Translator: SEIKO EPSON CORPORATION\n"
+"Language-Team: Spanish\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: \n"
+
+#: frontend/file-selector.cc:465
+msgid "Overwrite"
+msgstr "Sobreescribir"
+
+#: frontend/file-selector.cc:465
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: frontend/file-selector.cc:746
+msgid "Save Options"
+msgstr "Opciones de guardar"
+
+#: frontend/file-selector.cc:765
+msgid "Create file with all pages"
+msgstr "Crear arch. con todas pág."
+
+#: frontend/file-selector.cc:768
+msgid "Binding Position (for Double-Sided Scanning)"
+msgstr "Punto Encuadernación (Escaneo Doble Cara)"
+
+#: frontend/file-selector.cc:769
+msgid "Left"
+msgstr "Izquierda"
+
+#: frontend/file-selector.cc:771
+msgid "Top"
+msgstr "Arriba"
+
+#: frontend/file-selector.cc:776
+msgid "Determine File Type:"
+msgstr "Determinar tipo de fichero:"
+
+#: frontend/file-selector.cc:835
+msgid "Start filing at:"
+msgstr "Iniciar archivando en"
+
+#: frontend/file-selector.cc:858
+msgid "Number of digits:"
+msgstr "Número de dígitos"
+
+#: frontend/pisa_aleart_dialog.cc:73 frontend/pisa_configuration.cc:204
+#: frontend/pisa_scan_selector.cc:199
+msgid " OK "
+msgstr " OK "
+
+#: frontend/pisa_configuration.cc:113 frontend/pisa_main_window.cc:849
+msgid "Configuration"
+msgstr "Configuración"
+
+#: frontend/pisa_configuration.cc:163
+msgid "Print Command"
+msgstr "Orden de Impresión"
+
+#: frontend/pisa_configuration.cc:186
+msgid ""
+"In order to print, your print system must be able to handle the PNG file "
+"format directly. CUPS or Photo Image Print System (versions 1.3.1 or later) "
+"do this by default."
+msgstr ""
+
+#: frontend/pisa_configuration.cc:214 frontend/pisa_progress_window.cc:61
+#: frontend/pisa_scan_selector.cc:213 frontend/pisa_view_manager.cc:762
+msgid " Cancel "
+msgstr " Cancelar "
+
+#: frontend/pisa_error.cc:85
+msgid "Operation completed succesfully."
+msgstr ""
+
+#: frontend/pisa_error.cc:87
+msgid "Operation is not supported."
+msgstr ""
+
+#: frontend/pisa_error.cc:89
+msgid "Operation was cancelled."
+msgstr ""
+
+#: frontend/pisa_error.cc:91
+msgid "Device is busy---retry later."
+msgstr ""
+
+#: frontend/pisa_error.cc:93
+msgid "Data or argument is invalid."
+msgstr ""
+
+#: frontend/pisa_error.cc:95
+msgid "No more data available (end-of-file)."
+msgstr ""
+
+#: frontend/pisa_error.cc:97
+msgid ""
+"A paper jam occured. Open the Automatic Document Feeder and remove any "
+"paper."
+msgstr ""
+"\"Ha ocurrido un atasco de papel.\n"
+"Abra el alimentador automático de documentos y retire cualquier papel."
+
+#: frontend/pisa_error.cc:100
+msgid "Please load the document(s) into the Automatic Document Feeder."
+msgstr ""
+"Por favor, cargue el documento en el alimentador automático de documentos."
+
+#: frontend/pisa_error.cc:103
+msgid ""
+"The automatic document feeder or scanner unit is open.\n"
+"Please close it."
+msgstr ""
+"El alimentador automático de documentos o la unidad de escáner están "
+"abiertos. Por favor, ciérrelos."
+
+#: frontend/pisa_error.cc:106
+msgid "Error during device I/O."
+msgstr ""
+
+#: frontend/pisa_error.cc:108
+msgid "Out of memory."
+msgstr ""
+
+#: frontend/pisa_error.cc:110
+msgid "Access to resource has been denied."
+msgstr ""
+
+#: frontend/pisa_error.cc:113
+msgid "There is not enough disk space for operation"
+msgstr "No hay suficiente espacio libre en disco para realizar esta operación"
+
+#: frontend/pisa_error.cc:116
+msgid ""
+"Could not send command to scanner.\n"
+"Check the scanner's status."
+msgstr ""
+"No se puede enviar el comando al escáner.\n"
+"Compruebe el estado del escáner."
+
+#: frontend/pisa_error.cc:120
+msgid "Scanner model not supported"
+msgstr "Modelo de escáner no soportado"
+
+#: frontend/pisa_error.cc:123
+msgid ""
+"Selected area is too large for this resolution.\n"
+"Reduce the selected area or resolution."
+msgstr ""
+"El área seleccionada es demasiado grande para esta resolución.\n"
+"Reduzca los ajustes de resolución o escala, o reduzca el tamaño del área "
+"seleccionada."
+
+#: frontend/pisa_error.cc:127 frontend/pisa_error.cc:130
+msgid "Could not create file"
+msgstr "No se puede crear el archivo"
+
+#: frontend/pisa_error.cc:133
+msgid ""
+"A file with the same name already exists.\n"
+"Click \"Overwrite\" to replace the file or \"Cancel\" if you want to use "
+"another file name."
+msgstr ""
+"Ya existe un archivo con el mismo nombre.\n"
+"Haga clic en \"Sobreescribir\" para reemplazar el archivo o en \"Cancelar\" "
+"si desea usar otro nombre de archivo."
+
+#: frontend/pisa_error.cc:138
+msgid ""
+"The Image Type setting you selected cannot be used with this resolution.\n"
+"Reduce the Resolution or Scale setting."
+msgstr ""
+"El ajuste Tipo de Imagen que ha seleccionado no se puede utilizar con esta "
+"resolución.Reduzca el ajuste resolución o Escala."
+
+#: frontend/pisa_error.cc:143
+msgid "Tray cover is closed. Please open the tray cover and then scan again."
+msgstr ""
+"La cubierta de la bandeja está cerrada. Abra la cubierta e inténtelo de "
+"nuevo."
+
+#: frontend/pisa_error.cc:147
+msgid ""
+"A multi page feed occurred in the auto document feeder.\n"
+"Open the cover, remove the documents, and then try again. If documents "
+"remain on the tray, remove them and then reload them."
+msgstr ""
+"Se ha producido un error de alimentación múltiple de páginas en el "
+"alimentador automático de documentos.\n"
+"Abra la cubierta, retire los documentos e inténtelo de nuevo. Si quedan "
+"documentos en la bandeja, retírelos y vuelva a cargarlos."
+
+#: frontend/pisa_error.cc:156
+msgid "Unexpected error occurred"
+msgstr "Ocurrió error inesperado"
+
+#: frontend/pisa_gamma_correction.cc:482
+msgid " Reset "
+msgstr " Reiniciar "
+
+#: frontend/pisa_image_controls.cc:253
+msgid "Gamma:"
+msgstr "Gamma:"
+
+#: frontend/pisa_image_controls.cc:268
+msgid "Highlight:"
+msgstr "Luminosidad:"
+
+#: frontend/pisa_image_controls.cc:283
+msgid "Shadow:"
+msgstr "Sombra:"
+
+#: frontend/pisa_image_controls.cc:298
+msgid "Threshold:"
+msgstr "Umbral:"
+
+#: frontend/pisa_image_controls.cc:331
+msgid "Brightness:"
+msgstr "Brillo:"
+
+#: frontend/pisa_image_controls.cc:354
+msgid "Contrast:"
+msgstr "Contraste:"
+
+#: frontend/pisa_main_window.cc:90
+msgid "File"
+msgstr "Archivo"
+
+#: frontend/pisa_main_window.cc:91
+msgid "Printer"
+msgstr "Impresora"
+
+#: frontend/pisa_main_window.cc:104
+msgid "Flatbed"
+msgstr "Plano"
+
+#: frontend/pisa_main_window.cc:105
+msgid "TPU - Negative film"
+msgstr "TPU Film negativo"
+
+#: frontend/pisa_main_window.cc:106
+msgid "TPU - Positive film"
+msgstr "TPU Film positivo"
+
+#: frontend/pisa_main_window.cc:107
+msgid "ADF - Single-sided"
+msgstr "ADF - Una cara"
+
+#: frontend/pisa_main_window.cc:108
+msgid "ADF - Double-sided"
+msgstr "ADF - Doble cara"
+
+#: frontend/pisa_main_window.cc:121
+msgid "Color Photo"
+msgstr "Foto Color"
+
+#: frontend/pisa_main_window.cc:122
+msgid "Color Document"
+msgstr "Documento Color"
+
+#: frontend/pisa_main_window.cc:123
+msgid "Black & White Photo"
+msgstr "Foto Blanco y Negro"
+
+#: frontend/pisa_main_window.cc:124
+msgid "Black & White Document"
+msgstr "Documento Blanco y Negro"
+
+#: frontend/pisa_main_window.cc:125
+msgid "Line Art"
+msgstr "Dibujo"
+
+#: frontend/pisa_main_window.cc:169
+msgid "inches"
+msgstr "pulgadas"
+
+#: frontend/pisa_main_window.cc:170
+msgid "pixels"
+msgstr "píxels"
+
+#: frontend/pisa_main_window.cc:171
+msgid "cm"
+msgstr "cm"
+
+#: frontend/pisa_main_window.cc:192
+msgid "Off"
+msgstr "Desactivado"
+
+#: frontend/pisa_main_window.cc:193
+msgid "Standard"
+msgstr "Estándar"
+
+#: frontend/pisa_main_window.cc:194
+msgid "Thin"
+msgstr "Fino"
+
+#: frontend/pisa_main_window.cc:299
+msgid "Calibration is failed."
+msgstr "Ha fallado la calibración."
+
+#: frontend/pisa_main_window.cc:325
+msgid "Cleaning is failed."
+msgstr "Ha fallado la limpieza."
+
+#: frontend/pisa_main_window.cc:831
+msgid "Scan to File"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:837
+msgid "Scan"
+msgstr "Scan"
+
+#: frontend/pisa_main_window.cc:903
+msgid "Close"
+msgstr "Cerrar"
+
+#: frontend/pisa_main_window.cc:922
+msgid "Preview"
+msgstr "Previo"
+
+#: frontend/pisa_main_window.cc:923
+msgid "Zoom"
+msgstr "Zoom"
+
+#: frontend/pisa_main_window.cc:924
+msgid "Auto Exposure"
+msgstr "Auto Exposición"
+
+#: frontend/pisa_main_window.cc:927
+msgid "enable Start button"
+msgstr "activar botón Inicio"
+
+#: frontend/pisa_main_window.cc:928
+msgid "Speed priority scanning"
+msgstr "Escaneado rápido"
+
+#: frontend/pisa_main_window.cc:930
+msgid "Document Size - Auto Detect"
+msgstr "Tamaño de Documento - Detección Automática"
+
+#: frontend/pisa_main_window.cc:932
+msgid "Correct Document Skew"
+msgstr "Corrección de Sesgo"
+
+#: frontend/pisa_main_window.cc:937
+msgid "Unsharp mask"
+msgstr "Máscara Suave"
+
+#: frontend/pisa_main_window.cc:941
+msgid "Detect Double Feed (Paper Thickness)"
+msgstr "Detectar Alim. Doble (Grosor del Papel)"
+
+#: frontend/pisa_main_window.cc:1027
+msgid "Scan to Print"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1073
+msgid "Destination:"
+msgstr "Destino:"
+
+#: frontend/pisa_main_window.cc:1152 frontend/pisa_scan_selector.cc:142
+msgid "Scanner:"
+msgstr "Escáner:"
+
+#: frontend/pisa_main_window.cc:1175
+msgid "Document"
+msgstr "Documento"
+
+#: frontend/pisa_main_window.cc:1186
+msgid "Image Controls"
+msgstr "Controles de Imagen"
+
+#: frontend/pisa_main_window.cc:1195
+msgid "Tone Correction"
+msgstr "Corrección de Tono"
+
+#: frontend/pisa_main_window.cc:1233
+msgid "Document Source:"
+msgstr "Origen Documento:"
+
+#: frontend/pisa_main_window.cc:1266
+msgid "Image Type:"
+msgstr "Tipo Imagen:"
+
+#: frontend/pisa_main_window.cc:1278
+msgid "Resolution:"
+msgstr "Resolución:"
+
+#: frontend/pisa_main_window.cc:1327
+msgid "Target"
+msgstr "Tamaño"
+
+#: frontend/pisa_main_window.cc:1338
+msgid "W:"
+msgstr "An:"
+
+#: frontend/pisa_main_window.cc:1350
+msgid "H:"
+msgstr "Al:"
+
+#: frontend/pisa_main_window.cc:1375
+msgid "Scale"
+msgstr "Escala"
+
+#: frontend/pisa_main_window.cc:1399
+msgid "Focus"
+msgstr "Foco"
+
+#: frontend/pisa_main_window.cc:1429
+msgid "Options"
+msgstr "Opciones"
+
+#: frontend/pisa_main_window.cc:1720
+msgid "Calibration"
+msgstr "Calibración"
+
+#: frontend/pisa_main_window.cc:1731
+msgid "Cleaning"
+msgstr "Limpieza"
+
+#: frontend/pisa_progress_window.cc:62 frontend/pisa_view_manager.cc:774
+msgid " Finish "
+msgstr " Finalizar "
+
+#: frontend/pisa_progress_window.cc:171
+msgid "Scanner is warming up. Please wait..."
+msgstr "El escáner se está preparando. Por favor, espere..."
+
+#: frontend/pisa_progress_window.cc:174
+msgid "Pre-scanning in Progress"
+msgstr "Explorando"
+
+#: frontend/pisa_progress_window.cc:177
+msgid "Scanning in Progress"
+msgstr "Pre-exploración en proceso"
+
+#: frontend/pisa_progress_window.cc:180
+msgid ""
+"Starting a sequence of scans.\n"
+"Press to scanner's Start button to start each scan."
+msgstr ""
+"Iniciando la secuencia de escaneos.\n"
+"Pulse el botón Scan del escáner para iniciar cada escaneo."
+
+#: frontend/pisa_scan_selector.cc:103
+msgid "Scan Selector Dialog"
+msgstr ""
+
+#: frontend/pisa_scan_selector.cc:163
+msgid " Update "
+msgstr " Actualizar "
+
+#: frontend/pisa_view_manager.cc:762
+msgid " Continue "
+msgstr " Continuar "
+
+#~ msgid "Adjust"
+#~ msgstr "Ajuste"
diff --git a/po/fr.gmo b/po/fr.gmo
new file mode 100644
index 0000000..a8a26a3
--- /dev/null
+++ b/po/fr.gmo
Binary files differ
diff --git a/po/fr.po b/po/fr.po
new file mode 100644
index 0000000..793fbdd
--- /dev/null
+++ b/po/fr.po
@@ -0,0 +1,464 @@
+# fr.po -- French translations for `iscan' messages
+# Copyright (C) 2003, 2012 SEIKO EPSON CORPORATION
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: iscan 1.9.0\n"
+"Report-Msgid-Bugs-To: linux-printer@epson.jp\n"
+"POT-Creation-Date: 2019-08-21 08:06+0900\n"
+"PO-Revision-Date: 2011-05-30 16:11+0900\n"
+"Last-Translator: SEIKO EPSON CORPORATION\n"
+"Language-Team: French\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: \n"
+
+#: frontend/file-selector.cc:465
+msgid "Overwrite"
+msgstr "Écraser"
+
+#: frontend/file-selector.cc:465
+msgid "Cancel"
+msgstr "Annuler"
+
+#: frontend/file-selector.cc:746
+msgid "Save Options"
+msgstr "Options d'enregistrement"
+
+#: frontend/file-selector.cc:765
+msgid "Create file with all pages"
+msgstr "Conv. ttes pages en fichier"
+
+#: frontend/file-selector.cc:768
+msgid "Binding Position (for Double-Sided Scanning)"
+msgstr "Position de reliure (numérisation double)"
+
+#: frontend/file-selector.cc:769
+msgid "Left"
+msgstr "Gauche"
+
+#: frontend/file-selector.cc:771
+msgid "Top"
+msgstr "Haut"
+
+#: frontend/file-selector.cc:776
+msgid "Determine File Type:"
+msgstr "Déterminer le type du fichier:"
+
+#: frontend/file-selector.cc:835
+msgid "Start filing at:"
+msgstr "Numéroter à partir de"
+
+#: frontend/file-selector.cc:858
+msgid "Number of digits:"
+msgstr "Nombre de chiffres"
+
+#: frontend/pisa_aleart_dialog.cc:73 frontend/pisa_configuration.cc:204
+#: frontend/pisa_scan_selector.cc:199
+msgid " OK "
+msgstr " OK "
+
+#: frontend/pisa_configuration.cc:113 frontend/pisa_main_window.cc:849
+msgid "Configuration"
+msgstr "Configuration"
+
+#: frontend/pisa_configuration.cc:163
+msgid "Print Command"
+msgstr "Commande Imprimer"
+
+#: frontend/pisa_configuration.cc:186
+msgid ""
+"In order to print, your print system must be able to handle the PNG file "
+"format directly. CUPS or Photo Image Print System (versions 1.3.1 or later) "
+"do this by default."
+msgstr ""
+
+#: frontend/pisa_configuration.cc:214 frontend/pisa_progress_window.cc:61
+#: frontend/pisa_scan_selector.cc:213 frontend/pisa_view_manager.cc:762
+msgid " Cancel "
+msgstr " Annuler "
+
+#: frontend/pisa_error.cc:85
+msgid "Operation completed succesfully."
+msgstr ""
+
+#: frontend/pisa_error.cc:87
+msgid "Operation is not supported."
+msgstr ""
+
+#: frontend/pisa_error.cc:89
+msgid "Operation was cancelled."
+msgstr ""
+
+#: frontend/pisa_error.cc:91
+msgid "Device is busy---retry later."
+msgstr ""
+
+#: frontend/pisa_error.cc:93
+msgid "Data or argument is invalid."
+msgstr ""
+
+#: frontend/pisa_error.cc:95
+msgid "No more data available (end-of-file)."
+msgstr ""
+
+#: frontend/pisa_error.cc:97
+msgid ""
+"A paper jam occured. Open the Automatic Document Feeder and remove any "
+"paper."
+msgstr ""
+"Un bourrage papier s'est produit.\n"
+"Ouvrez le chargeur feuille à feuille et retirez tout le papier."
+
+#: frontend/pisa_error.cc:100
+msgid "Please load the document(s) into the Automatic Document Feeder."
+msgstr "Veuillez charger le(s) document(s) dans le chargeur feuille à feuille."
+
+#: frontend/pisa_error.cc:103
+msgid ""
+"The automatic document feeder or scanner unit is open.\n"
+"Please close it."
+msgstr ""
+"Le chargeur feuille à feuille ou l'unité scanner est ouvert(e). Veuillez le "
+"(la) refermer."
+
+#: frontend/pisa_error.cc:106
+msgid "Error during device I/O."
+msgstr ""
+
+#: frontend/pisa_error.cc:108
+msgid "Out of memory."
+msgstr ""
+
+#: frontend/pisa_error.cc:110
+msgid "Access to resource has been denied."
+msgstr ""
+
+#: frontend/pisa_error.cc:113
+msgid "There is not enough disk space for operation"
+msgstr "Espace disque insuffisant pour le déroulement de l'opération"
+
+#: frontend/pisa_error.cc:116
+msgid ""
+"Could not send command to scanner.\n"
+"Check the scanner's status."
+msgstr ""
+"Impossible d'envoyer une commande au scanner. Vérifiez l'état du scanner."
+
+#: frontend/pisa_error.cc:120
+msgid "Scanner model not supported"
+msgstr "Ce modèle de scanner n'est pas supporté"
+
+#: frontend/pisa_error.cc:123
+msgid ""
+"Selected area is too large for this resolution.\n"
+"Reduce the selected area or resolution."
+msgstr ""
+"La zone sélectionnée est trop grande pour cette résolution.\n"
+"Réduisez le paramètre Résolution ou Echelle, ou diminuez la taille de la "
+"zone sélectionnée."
+
+#: frontend/pisa_error.cc:127 frontend/pisa_error.cc:130
+msgid "Could not create file"
+msgstr "Création du fichier impossible"
+
+#: frontend/pisa_error.cc:133
+msgid ""
+"A file with the same name already exists.\n"
+"Click \"Overwrite\" to replace the file or \"Cancel\" if you want to use "
+"another file name."
+msgstr ""
+"Un fichier avec le même nom existe déjà.\n"
+"Cliquez sur \"Écraser\" pour remplacer le fichier ou \"Annuler\" pour "
+"utiliser un autre nom de fichier."
+
+#: frontend/pisa_error.cc:138
+msgid ""
+"The Image Type setting you selected cannot be used with this resolution.\n"
+"Reduce the Resolution or Scale setting."
+msgstr ""
+"Le paramétrage du type d'image que vous avez sélectionné ne peut être "
+"utilisé à cette résolution. Veuillez réduire la résolution ou le facteur "
+"d'échelle."
+
+#: frontend/pisa_error.cc:143
+msgid "Tray cover is closed. Please open the tray cover and then scan again."
+msgstr "Le couvercle du bac est fermé. Ouvrez le couvercle et réessayez."
+
+#: frontend/pisa_error.cc:147
+msgid ""
+"A multi page feed occurred in the auto document feeder.\n"
+"Open the cover, remove the documents, and then try again. If documents "
+"remain on the tray, remove them and then reload them."
+msgstr ""
+"Le chargeur a pris plusieurs pages en même temps.\n"
+"Ouvrez le capot, retirez les documents et réessayez. Si les documents "
+"restent sur le plateau, retirez-les, puis rechargez-les."
+
+#: frontend/pisa_error.cc:156
+msgid "Unexpected error occurred"
+msgstr "Une erreur inattendue est intervenue"
+
+#: frontend/pisa_gamma_correction.cc:482
+msgid " Reset "
+msgstr " Reset "
+
+#: frontend/pisa_image_controls.cc:253
+msgid "Gamma:"
+msgstr "Gamma:"
+
+#: frontend/pisa_image_controls.cc:268
+msgid "Highlight:"
+msgstr "Haute Lumière:"
+
+#: frontend/pisa_image_controls.cc:283
+msgid "Shadow:"
+msgstr "Ombre:"
+
+#: frontend/pisa_image_controls.cc:298
+msgid "Threshold:"
+msgstr "Seuil Noir/Blanc"
+
+#: frontend/pisa_image_controls.cc:331
+msgid "Brightness:"
+msgstr "Luminosité:"
+
+#: frontend/pisa_image_controls.cc:354
+msgid "Contrast:"
+msgstr "Contraste:"
+
+#: frontend/pisa_main_window.cc:90
+msgid "File"
+msgstr "Fichier"
+
+#: frontend/pisa_main_window.cc:91
+msgid "Printer"
+msgstr "Imprimante"
+
+#: frontend/pisa_main_window.cc:104
+msgid "Flatbed"
+msgstr "A plat / Opaque"
+
+#: frontend/pisa_main_window.cc:105
+msgid "TPU - Negative film"
+msgstr "Trans. Film nég."
+
+#: frontend/pisa_main_window.cc:106
+msgid "TPU - Positive film"
+msgstr "Trans. Film pos."
+
+#: frontend/pisa_main_window.cc:107
+msgid "ADF - Single-sided"
+msgstr "Chargeur - simple"
+
+#: frontend/pisa_main_window.cc:108
+msgid "ADF - Double-sided"
+msgstr "Chargeur - double"
+
+#: frontend/pisa_main_window.cc:121
+msgid "Color Photo"
+msgstr "Photo Couleur"
+
+#: frontend/pisa_main_window.cc:122
+msgid "Color Document"
+msgstr "Document Couleur"
+
+#: frontend/pisa_main_window.cc:123
+msgid "Black & White Photo"
+msgstr "Photo Noir & Blanc"
+
+#: frontend/pisa_main_window.cc:124
+msgid "Black & White Document"
+msgstr "Document Noir & Blanc"
+
+#: frontend/pisa_main_window.cc:125
+msgid "Line Art"
+msgstr "Au trait/OCR standard"
+
+#: frontend/pisa_main_window.cc:169
+msgid "inches"
+msgstr "pouces"
+
+#: frontend/pisa_main_window.cc:170
+msgid "pixels"
+msgstr "pixels"
+
+#: frontend/pisa_main_window.cc:171
+msgid "cm"
+msgstr "cm"
+
+#: frontend/pisa_main_window.cc:192
+msgid "Off"
+msgstr "Dim. Source"
+
+#: frontend/pisa_main_window.cc:193
+msgid "Standard"
+msgstr "Standard"
+
+#: frontend/pisa_main_window.cc:194
+msgid "Thin"
+msgstr "Fin"
+
+#: frontend/pisa_main_window.cc:299
+msgid "Calibration is failed."
+msgstr "Échec du calibrage."
+
+#: frontend/pisa_main_window.cc:325
+msgid "Cleaning is failed."
+msgstr "Échec du nettoyage."
+
+#: frontend/pisa_main_window.cc:831
+msgid "Scan to File"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:837
+msgid "Scan"
+msgstr "Numériser"
+
+#: frontend/pisa_main_window.cc:903
+msgid "Close"
+msgstr "Fermer"
+
+#: frontend/pisa_main_window.cc:922
+msgid "Preview"
+msgstr "Aperçu"
+
+#: frontend/pisa_main_window.cc:923
+msgid "Zoom"
+msgstr "Zoom"
+
+#: frontend/pisa_main_window.cc:924
+msgid "Auto Exposure"
+msgstr "Exposition auto"
+
+#: frontend/pisa_main_window.cc:927
+msgid "enable Start button"
+msgstr "Activer la touche AutoScan"
+
+#: frontend/pisa_main_window.cc:928
+msgid "Speed priority scanning"
+msgstr "Analyse de la priorité du débit"
+
+#: frontend/pisa_main_window.cc:930
+msgid "Document Size - Auto Detect"
+msgstr "Taille du document - Détection automatique"
+
+#: frontend/pisa_main_window.cc:932
+msgid "Correct Document Skew"
+msgstr "Corriger document en biais"
+
+#: frontend/pisa_main_window.cc:937
+msgid "Unsharp mask"
+msgstr "Netteté"
+
+#: frontend/pisa_main_window.cc:941
+msgid "Detect Double Feed (Paper Thickness)"
+msgstr "Détecter alimentation double (Épaisseur papier)"
+
+#: frontend/pisa_main_window.cc:1027
+msgid "Scan to Print"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1073
+msgid "Destination:"
+msgstr "Destination:"
+
+#: frontend/pisa_main_window.cc:1152 frontend/pisa_scan_selector.cc:142
+msgid "Scanner:"
+msgstr "Scanner:"
+
+#: frontend/pisa_main_window.cc:1175
+msgid "Document"
+msgstr "Document"
+
+#: frontend/pisa_main_window.cc:1186
+msgid "Image Controls"
+msgstr "Contrôle des images"
+
+#: frontend/pisa_main_window.cc:1195
+msgid "Tone Correction"
+msgstr "Correction tonale"
+
+#: frontend/pisa_main_window.cc:1233
+msgid "Document Source:"
+msgstr "Source du document:"
+
+#: frontend/pisa_main_window.cc:1266
+msgid "Image Type:"
+msgstr "Type d'image:"
+
+#: frontend/pisa_main_window.cc:1278
+msgid "Resolution:"
+msgstr "Résolution:"
+
+#: frontend/pisa_main_window.cc:1327
+msgid "Target"
+msgstr "Dim. Sortie"
+
+#: frontend/pisa_main_window.cc:1338
+msgid "W:"
+msgstr "L:"
+
+#: frontend/pisa_main_window.cc:1350
+msgid "H:"
+msgstr "H:"
+
+#: frontend/pisa_main_window.cc:1375
+msgid "Scale"
+msgstr "Echelle"
+
+#: frontend/pisa_main_window.cc:1399
+msgid "Focus"
+msgstr "Mise au point"
+
+#: frontend/pisa_main_window.cc:1429
+msgid "Options"
+msgstr "Options"
+
+#: frontend/pisa_main_window.cc:1720
+msgid "Calibration"
+msgstr "Calibrage"
+
+#: frontend/pisa_main_window.cc:1731
+msgid "Cleaning"
+msgstr "Nettoyage"
+
+#: frontend/pisa_progress_window.cc:62 frontend/pisa_view_manager.cc:774
+msgid " Finish "
+msgstr " Terminer "
+
+#: frontend/pisa_progress_window.cc:171
+msgid "Scanner is warming up. Please wait..."
+msgstr "Le scanner s'initialise. Patientez..."
+
+#: frontend/pisa_progress_window.cc:174
+msgid "Pre-scanning in Progress"
+msgstr "Pré-numérisation en cours"
+
+#: frontend/pisa_progress_window.cc:177
+msgid "Scanning in Progress"
+msgstr "Numérisation en cours"
+
+#: frontend/pisa_progress_window.cc:180
+msgid ""
+"Starting a sequence of scans.\n"
+"Press to scanner's Start button to start each scan."
+msgstr ""
+"Démarrage d'une séquence de numérisation. Appuyez sur la touche AutoScan du "
+"scanner pour démarrer chaque numérisation."
+
+#: frontend/pisa_scan_selector.cc:103
+msgid "Scan Selector Dialog"
+msgstr ""
+
+#: frontend/pisa_scan_selector.cc:163
+msgid " Update "
+msgstr " Mettre à jour "
+
+#: frontend/pisa_view_manager.cc:762
+msgid " Continue "
+msgstr " Continuer "
+
+#~ msgid "Adjust"
+#~ msgstr "Ajuster"
diff --git a/po/insert-header.sin b/po/insert-header.sin
new file mode 100644
index 0000000..b26de01
--- /dev/null
+++ b/po/insert-header.sin
@@ -0,0 +1,23 @@
+# Sed script that inserts the file called HEADER before the header entry.
+#
+# At each occurrence of a line starting with "msgid ", we execute the following
+# commands. At the first occurrence, insert the file. At the following
+# occurrences, do nothing. The distinction between the first and the following
+# occurrences is achieved by looking at the hold space.
+/^msgid /{
+x
+# Test if the hold space is empty.
+s/m/m/
+ta
+# Yes it was empty. First occurrence. Read the file.
+r HEADER
+# Output the file's contents by reading the next line. But don't lose the
+# current line while doing this.
+g
+N
+bb
+:a
+# The hold space was nonempty. Following occurrences. Do nothing.
+x
+:b
+}
diff --git a/po/iscan.pot b/po/iscan.pot
new file mode 100644
index 0000000..c17ffb8
--- /dev/null
+++ b/po/iscan.pot
@@ -0,0 +1,444 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR SEIKO EPSON CORPORATION
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: linux-printer@epson.jp\n"
+"POT-Creation-Date: 2019-08-21 08:06+0900\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: frontend/file-selector.cc:465
+msgid "Overwrite"
+msgstr ""
+
+#: frontend/file-selector.cc:465
+msgid "Cancel"
+msgstr ""
+
+#: frontend/file-selector.cc:746
+msgid "Save Options"
+msgstr ""
+
+#: frontend/file-selector.cc:765
+msgid "Create file with all pages"
+msgstr ""
+
+#: frontend/file-selector.cc:768
+msgid "Binding Position (for Double-Sided Scanning)"
+msgstr ""
+
+#: frontend/file-selector.cc:769
+msgid "Left"
+msgstr ""
+
+#: frontend/file-selector.cc:771
+msgid "Top"
+msgstr ""
+
+#: frontend/file-selector.cc:776
+msgid "Determine File Type:"
+msgstr ""
+
+#: frontend/file-selector.cc:835
+msgid "Start filing at:"
+msgstr ""
+
+#: frontend/file-selector.cc:858
+msgid "Number of digits:"
+msgstr ""
+
+#: frontend/pisa_aleart_dialog.cc:73 frontend/pisa_configuration.cc:204
+#: frontend/pisa_scan_selector.cc:199
+msgid " OK "
+msgstr ""
+
+#: frontend/pisa_configuration.cc:113 frontend/pisa_main_window.cc:849
+msgid "Configuration"
+msgstr ""
+
+#: frontend/pisa_configuration.cc:163
+msgid "Print Command"
+msgstr ""
+
+#: frontend/pisa_configuration.cc:186
+msgid ""
+"In order to print, your print system must be able to handle the PNG file "
+"format directly. CUPS or Photo Image Print System (versions 1.3.1 or later) "
+"do this by default."
+msgstr ""
+
+#: frontend/pisa_configuration.cc:214 frontend/pisa_progress_window.cc:61
+#: frontend/pisa_scan_selector.cc:213 frontend/pisa_view_manager.cc:762
+msgid " Cancel "
+msgstr ""
+
+#: frontend/pisa_error.cc:85
+msgid "Operation completed succesfully."
+msgstr ""
+
+#: frontend/pisa_error.cc:87
+msgid "Operation is not supported."
+msgstr ""
+
+#: frontend/pisa_error.cc:89
+msgid "Operation was cancelled."
+msgstr ""
+
+#: frontend/pisa_error.cc:91
+msgid "Device is busy---retry later."
+msgstr ""
+
+#: frontend/pisa_error.cc:93
+msgid "Data or argument is invalid."
+msgstr ""
+
+#: frontend/pisa_error.cc:95
+msgid "No more data available (end-of-file)."
+msgstr ""
+
+#: frontend/pisa_error.cc:97
+msgid ""
+"A paper jam occured. Open the Automatic Document Feeder and remove any "
+"paper."
+msgstr ""
+
+#: frontend/pisa_error.cc:100
+msgid "Please load the document(s) into the Automatic Document Feeder."
+msgstr ""
+
+#: frontend/pisa_error.cc:103
+msgid ""
+"The automatic document feeder or scanner unit is open.\n"
+"Please close it."
+msgstr ""
+
+#: frontend/pisa_error.cc:106
+msgid "Error during device I/O."
+msgstr ""
+
+#: frontend/pisa_error.cc:108
+msgid "Out of memory."
+msgstr ""
+
+#: frontend/pisa_error.cc:110
+msgid "Access to resource has been denied."
+msgstr ""
+
+#: frontend/pisa_error.cc:113
+msgid "There is not enough disk space for operation"
+msgstr ""
+
+#: frontend/pisa_error.cc:116
+msgid ""
+"Could not send command to scanner.\n"
+"Check the scanner's status."
+msgstr ""
+
+#: frontend/pisa_error.cc:120
+msgid "Scanner model not supported"
+msgstr ""
+
+#: frontend/pisa_error.cc:123
+msgid ""
+"Selected area is too large for this resolution.\n"
+"Reduce the selected area or resolution."
+msgstr ""
+
+#: frontend/pisa_error.cc:127 frontend/pisa_error.cc:130
+msgid "Could not create file"
+msgstr ""
+
+#: frontend/pisa_error.cc:133
+msgid ""
+"A file with the same name already exists.\n"
+"Click \"Overwrite\" to replace the file or \"Cancel\" if you want to use "
+"another file name."
+msgstr ""
+
+#: frontend/pisa_error.cc:138
+msgid ""
+"The Image Type setting you selected cannot be used with this resolution.\n"
+"Reduce the Resolution or Scale setting."
+msgstr ""
+
+#: frontend/pisa_error.cc:143
+msgid "Tray cover is closed. Please open the tray cover and then scan again."
+msgstr ""
+
+#: frontend/pisa_error.cc:147
+msgid ""
+"A multi page feed occurred in the auto document feeder.\n"
+"Open the cover, remove the documents, and then try again. If documents "
+"remain on the tray, remove them and then reload them."
+msgstr ""
+
+#: frontend/pisa_error.cc:156
+msgid "Unexpected error occurred"
+msgstr ""
+
+#: frontend/pisa_gamma_correction.cc:482
+msgid " Reset "
+msgstr ""
+
+#: frontend/pisa_image_controls.cc:253
+msgid "Gamma:"
+msgstr ""
+
+#: frontend/pisa_image_controls.cc:268
+msgid "Highlight:"
+msgstr ""
+
+#: frontend/pisa_image_controls.cc:283
+msgid "Shadow:"
+msgstr ""
+
+#: frontend/pisa_image_controls.cc:298
+msgid "Threshold:"
+msgstr ""
+
+#: frontend/pisa_image_controls.cc:331
+msgid "Brightness:"
+msgstr ""
+
+#: frontend/pisa_image_controls.cc:354
+msgid "Contrast:"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:90
+msgid "File"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:91
+msgid "Printer"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:104
+msgid "Flatbed"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:105
+msgid "TPU - Negative film"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:106
+msgid "TPU - Positive film"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:107
+msgid "ADF - Single-sided"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:108
+msgid "ADF - Double-sided"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:121
+msgid "Color Photo"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:122
+msgid "Color Document"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:123
+msgid "Black & White Photo"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:124
+msgid "Black & White Document"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:125
+msgid "Line Art"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:169
+msgid "inches"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:170
+msgid "pixels"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:171
+msgid "cm"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:192
+msgid "Off"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:193
+msgid "Standard"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:194
+msgid "Thin"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:299
+msgid "Calibration is failed."
+msgstr ""
+
+#: frontend/pisa_main_window.cc:325
+msgid "Cleaning is failed."
+msgstr ""
+
+#: frontend/pisa_main_window.cc:831
+msgid "Scan to File"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:837
+msgid "Scan"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:903
+msgid "Close"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:922
+msgid "Preview"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:923
+msgid "Zoom"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:924
+msgid "Auto Exposure"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:927
+msgid "enable Start button"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:928
+msgid "Speed priority scanning"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:930
+msgid "Document Size - Auto Detect"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:932
+msgid "Correct Document Skew"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:937
+msgid "Unsharp mask"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:941
+msgid "Detect Double Feed (Paper Thickness)"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1027
+msgid "Scan to Print"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1073
+msgid "Destination:"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1152 frontend/pisa_scan_selector.cc:142
+msgid "Scanner:"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1175
+msgid "Document"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1186
+msgid "Image Controls"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1195
+msgid "Tone Correction"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1233
+msgid "Document Source:"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1266
+msgid "Image Type:"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1278
+msgid "Resolution:"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1327
+msgid "Target"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1338
+msgid "W:"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1350
+msgid "H:"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1375
+msgid "Scale"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1399
+msgid "Focus"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1429
+msgid "Options"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1720
+msgid "Calibration"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1731
+msgid "Cleaning"
+msgstr ""
+
+#: frontend/pisa_progress_window.cc:62 frontend/pisa_view_manager.cc:774
+msgid " Finish "
+msgstr ""
+
+#: frontend/pisa_progress_window.cc:171
+msgid "Scanner is warming up. Please wait..."
+msgstr ""
+
+#: frontend/pisa_progress_window.cc:174
+msgid "Pre-scanning in Progress"
+msgstr ""
+
+#: frontend/pisa_progress_window.cc:177
+msgid "Scanning in Progress"
+msgstr ""
+
+#: frontend/pisa_progress_window.cc:180
+msgid ""
+"Starting a sequence of scans.\n"
+"Press to scanner's Start button to start each scan."
+msgstr ""
+
+#: frontend/pisa_scan_selector.cc:103
+msgid "Scan Selector Dialog"
+msgstr ""
+
+#: frontend/pisa_scan_selector.cc:163
+msgid " Update "
+msgstr ""
+
+#: frontend/pisa_view_manager.cc:762
+msgid " Continue "
+msgstr ""
diff --git a/po/it.gmo b/po/it.gmo
new file mode 100644
index 0000000..5452909
--- /dev/null
+++ b/po/it.gmo
Binary files differ
diff --git a/po/it.po b/po/it.po
new file mode 100644
index 0000000..49b841d
--- /dev/null
+++ b/po/it.po
@@ -0,0 +1,465 @@
+# it.po -- Italian translations for `iscan' messages
+# Copyright (C) 2003, 2012 SEIKO EPSON CORPORATION
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: iscan 1.9.0\n"
+"Report-Msgid-Bugs-To: linux-printer@epson.jp\n"
+"POT-Creation-Date: 2019-08-21 08:06+0900\n"
+"PO-Revision-Date: 2011-05-30 15:52+0900\n"
+"Last-Translator: SEIKO EPSON CORPORATION\n"
+"Language-Team: Italian\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: \n"
+
+#: frontend/file-selector.cc:465
+msgid "Overwrite"
+msgstr "Sovrascrivi"
+
+#: frontend/file-selector.cc:465
+msgid "Cancel"
+msgstr "Annulla"
+
+#: frontend/file-selector.cc:746
+msgid "Save Options"
+msgstr "Salva opzioni"
+
+#: frontend/file-selector.cc:765
+msgid "Create file with all pages"
+msgstr "File unico (tutte le pag.)"
+
+#: frontend/file-selector.cc:768
+msgid "Binding Position (for Double-Sided Scanning)"
+msgstr "Posizione Rilegatura (Scans. Fronte-Retro)"
+
+#: frontend/file-selector.cc:769
+msgid "Left"
+msgstr "Sinistra"
+
+#: frontend/file-selector.cc:771
+msgid "Top"
+msgstr "Alto"
+
+#: frontend/file-selector.cc:776
+msgid "Determine File Type:"
+msgstr "Determina tipo file:"
+
+#: frontend/file-selector.cc:835
+msgid "Start filing at:"
+msgstr "Inizia archiviazione da"
+
+#: frontend/file-selector.cc:858
+msgid "Number of digits:"
+msgstr "Numero di cifre"
+
+#: frontend/pisa_aleart_dialog.cc:73 frontend/pisa_configuration.cc:204
+#: frontend/pisa_scan_selector.cc:199
+msgid " OK "
+msgstr " OK "
+
+#: frontend/pisa_configuration.cc:113 frontend/pisa_main_window.cc:849
+msgid "Configuration"
+msgstr "Configurazione"
+
+#: frontend/pisa_configuration.cc:163
+msgid "Print Command"
+msgstr "Stampa"
+
+#: frontend/pisa_configuration.cc:186
+msgid ""
+"In order to print, your print system must be able to handle the PNG file "
+"format directly. CUPS or Photo Image Print System (versions 1.3.1 or later) "
+"do this by default."
+msgstr ""
+
+#: frontend/pisa_configuration.cc:214 frontend/pisa_progress_window.cc:61
+#: frontend/pisa_scan_selector.cc:213 frontend/pisa_view_manager.cc:762
+msgid " Cancel "
+msgstr " Annulla "
+
+#: frontend/pisa_error.cc:85
+msgid "Operation completed succesfully."
+msgstr ""
+
+#: frontend/pisa_error.cc:87
+msgid "Operation is not supported."
+msgstr ""
+
+#: frontend/pisa_error.cc:89
+msgid "Operation was cancelled."
+msgstr ""
+
+#: frontend/pisa_error.cc:91
+msgid "Device is busy---retry later."
+msgstr ""
+
+#: frontend/pisa_error.cc:93
+msgid "Data or argument is invalid."
+msgstr ""
+
+#: frontend/pisa_error.cc:95
+msgid "No more data available (end-of-file)."
+msgstr ""
+
+#: frontend/pisa_error.cc:97
+msgid ""
+"A paper jam occured. Open the Automatic Document Feeder and remove any "
+"paper."
+msgstr ""
+"Inceppamento carta. Aprire l' Alimentatore Automatico di Documenti e "
+"rimuovere manualmente la carta."
+
+#: frontend/pisa_error.cc:100
+msgid "Please load the document(s) into the Automatic Document Feeder."
+msgstr "Prego inserire i documenti sull' Alimentatore Automatico di Documenti."
+
+#: frontend/pisa_error.cc:103
+msgid ""
+"The automatic document feeder or scanner unit is open.\n"
+"Please close it."
+msgstr ""
+"L'alimentatore automatico documenti, o l'unità scanner, è aperto.\n"
+"Prego, chiuderlo."
+
+#: frontend/pisa_error.cc:106
+msgid "Error during device I/O."
+msgstr ""
+
+#: frontend/pisa_error.cc:108
+msgid "Out of memory."
+msgstr ""
+
+#: frontend/pisa_error.cc:110
+msgid "Access to resource has been denied."
+msgstr ""
+
+#: frontend/pisa_error.cc:113
+msgid "There is not enough disk space for operation"
+msgstr "Non c'è sufficiente spazio su disco per l'operazione"
+
+#: frontend/pisa_error.cc:116
+msgid ""
+"Could not send command to scanner.\n"
+"Check the scanner's status."
+msgstr ""
+"Impossibili inviare comandi allo scanner.\n"
+"Controllare lo stato dello scanner."
+
+#: frontend/pisa_error.cc:120
+msgid "Scanner model not supported"
+msgstr "Modello di scanner non supportato"
+
+#: frontend/pisa_error.cc:123
+msgid ""
+"Selected area is too large for this resolution.\n"
+"Reduce the selected area or resolution."
+msgstr ""
+"L' area selezionata è troppo grande per questa risoluzione.\n"
+"Ridurre la risoluzione o la scala, oppure diminuire l' ampiezza dell' area "
+"selezionata."
+
+#: frontend/pisa_error.cc:127 frontend/pisa_error.cc:130
+msgid "Could not create file"
+msgstr "Impossibile creare il file"
+
+#: frontend/pisa_error.cc:133
+msgid ""
+"A file with the same name already exists.\n"
+"Click \"Overwrite\" to replace the file or \"Cancel\" if you want to use "
+"another file name."
+msgstr ""
+"Esiste già un file con lo stesso nome.\n"
+"Fare clic su \"Sovrascrivi\" per sovrascrivere il file oppure su \"Annulla\" "
+"per salvare il file con un nome differente."
+
+#: frontend/pisa_error.cc:138
+msgid ""
+"The Image Type setting you selected cannot be used with this resolution.\n"
+"Reduce the Resolution or Scale setting."
+msgstr ""
+"L'impostazione Tipo immagine selezionata, non può essere usata a questa "
+"risoluzione.Riducete la risoluzione o l'impostazione di scala."
+
+#: frontend/pisa_error.cc:143
+msgid "Tray cover is closed. Please open the tray cover and then scan again."
+msgstr "Lo sportello del vassoio è chiuso. Aprire lo sportello e riprovare."
+
+#: frontend/pisa_error.cc:147
+msgid ""
+"A multi page feed occurred in the auto document feeder.\n"
+"Open the cover, remove the documents, and then try again. If documents "
+"remain on the tray, remove them and then reload them."
+msgstr ""
+"Si è verificato un errore di alimentazione di più fogli nell'alimentatore "
+"automatico documenti.\n"
+"Aprire il coperchio, rimuovere i documenti e riprovare. Se i documenti "
+"rimangono nel vassoio, rimuoverli e caricarli di nuovo."
+
+#: frontend/pisa_error.cc:156
+msgid "Unexpected error occurred"
+msgstr "Si è verificato un errore non previsto"
+
+#: frontend/pisa_gamma_correction.cc:482
+msgid " Reset "
+msgstr " Reset "
+
+#: frontend/pisa_image_controls.cc:253
+msgid "Gamma:"
+msgstr "Gamma:"
+
+#: frontend/pisa_image_controls.cc:268
+msgid "Highlight:"
+msgstr "Alte luci"
+
+#: frontend/pisa_image_controls.cc:283
+msgid "Shadow:"
+msgstr "Ombre:"
+
+#: frontend/pisa_image_controls.cc:298
+msgid "Threshold:"
+msgstr "Soglia:"
+
+#: frontend/pisa_image_controls.cc:331
+msgid "Brightness:"
+msgstr "Luminosità:"
+
+#: frontend/pisa_image_controls.cc:354
+msgid "Contrast:"
+msgstr "Contrasto:"
+
+#: frontend/pisa_main_window.cc:90
+msgid "File"
+msgstr "File"
+
+#: frontend/pisa_main_window.cc:91
+msgid "Printer"
+msgstr "Stampante"
+
+#: frontend/pisa_main_window.cc:104
+msgid "Flatbed"
+msgstr "Piano"
+
+#: frontend/pisa_main_window.cc:105
+msgid "TPU - Negative film"
+msgstr "Dispositivo per pellicole negative"
+
+#: frontend/pisa_main_window.cc:106
+msgid "TPU - Positive film"
+msgstr "Dispositivo per pellicole positive"
+
+#: frontend/pisa_main_window.cc:107
+msgid "ADF - Single-sided"
+msgstr "ADF - Fronte"
+
+#: frontend/pisa_main_window.cc:108
+msgid "ADF - Double-sided"
+msgstr "ADF - Fronte-Retro"
+
+#: frontend/pisa_main_window.cc:121
+msgid "Color Photo"
+msgstr "Foto a colori"
+
+#: frontend/pisa_main_window.cc:122
+msgid "Color Document"
+msgstr "Documento a colori"
+
+#: frontend/pisa_main_window.cc:123
+msgid "Black & White Photo"
+msgstr "Foto in Bianco & Nero"
+
+#: frontend/pisa_main_window.cc:124
+msgid "Black & White Document"
+msgstr "Documento bianco e nero"
+
+#: frontend/pisa_main_window.cc:125
+msgid "Line Art"
+msgstr "Tratto"
+
+#: frontend/pisa_main_window.cc:169
+msgid "inches"
+msgstr "pollici"
+
+#: frontend/pisa_main_window.cc:170
+msgid "pixels"
+msgstr "pixel"
+
+#: frontend/pisa_main_window.cc:171
+msgid "cm"
+msgstr "cm"
+
+#: frontend/pisa_main_window.cc:192
+msgid "Off"
+msgstr "Off"
+
+#: frontend/pisa_main_window.cc:193
+msgid "Standard"
+msgstr "Standard"
+
+#: frontend/pisa_main_window.cc:194
+msgid "Thin"
+msgstr "Sottile"
+
+#: frontend/pisa_main_window.cc:299
+msgid "Calibration is failed."
+msgstr "Calibratura non riuscita."
+
+#: frontend/pisa_main_window.cc:325
+msgid "Cleaning is failed."
+msgstr "Pulizia non riuscita."
+
+#: frontend/pisa_main_window.cc:831
+msgid "Scan to File"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:837
+msgid "Scan"
+msgstr "Acquisisci"
+
+#: frontend/pisa_main_window.cc:903
+msgid "Close"
+msgstr "Chiudi"
+
+#: frontend/pisa_main_window.cc:922
+msgid "Preview"
+msgstr "Anteprima"
+
+#: frontend/pisa_main_window.cc:923
+msgid "Zoom"
+msgstr "Zoom"
+
+#: frontend/pisa_main_window.cc:924
+msgid "Auto Exposure"
+msgstr "Esposizione Automatica"
+
+#: frontend/pisa_main_window.cc:927
+msgid "enable Start button"
+msgstr "Abilitare il tasto Avvio."
+
+#: frontend/pisa_main_window.cc:928
+msgid "Speed priority scanning"
+msgstr "Scansione priorità velocità"
+
+#: frontend/pisa_main_window.cc:930
+msgid "Document Size - Auto Detect"
+msgstr "Dimensione Documento - Rilevamento Automatico"
+
+#: frontend/pisa_main_window.cc:932
+msgid "Correct Document Skew"
+msgstr "Correggi Allineamento Doc."
+
+#: frontend/pisa_main_window.cc:937
+msgid "Unsharp mask"
+msgstr "Maschera definizione dettagli"
+
+#: frontend/pisa_main_window.cc:941
+msgid "Detect Double Feed (Paper Thickness)"
+msgstr "Rileva Alim. Doppio Foglio (Spessore Carta)"
+
+#: frontend/pisa_main_window.cc:1027
+msgid "Scan to Print"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1073
+msgid "Destination:"
+msgstr "Destinazione:"
+
+#: frontend/pisa_main_window.cc:1152 frontend/pisa_scan_selector.cc:142
+msgid "Scanner:"
+msgstr "Scanner:"
+
+#: frontend/pisa_main_window.cc:1175
+msgid "Document"
+msgstr "Documento"
+
+#: frontend/pisa_main_window.cc:1186
+msgid "Image Controls"
+msgstr "Controlli immagine"
+
+#: frontend/pisa_main_window.cc:1195
+msgid "Tone Correction"
+msgstr "Correzione tono"
+
+#: frontend/pisa_main_window.cc:1233
+msgid "Document Source:"
+msgstr "Origine documento:"
+
+#: frontend/pisa_main_window.cc:1266
+msgid "Image Type:"
+msgstr "Tipo immagine:"
+
+#: frontend/pisa_main_window.cc:1278
+msgid "Resolution:"
+msgstr "Risoluzione:"
+
+#: frontend/pisa_main_window.cc:1327
+msgid "Target"
+msgstr "Destinazione"
+
+#: frontend/pisa_main_window.cc:1338
+msgid "W:"
+msgstr "L:"
+
+#: frontend/pisa_main_window.cc:1350
+msgid "H:"
+msgstr "H:"
+
+#: frontend/pisa_main_window.cc:1375
+msgid "Scale"
+msgstr "Ingrandimento/riduzione"
+
+#: frontend/pisa_main_window.cc:1399
+msgid "Focus"
+msgstr "Fuoco"
+
+#: frontend/pisa_main_window.cc:1429
+msgid "Options"
+msgstr "Opzioni"
+
+#: frontend/pisa_main_window.cc:1720
+msgid "Calibration"
+msgstr "Calibratura"
+
+#: frontend/pisa_main_window.cc:1731
+msgid "Cleaning"
+msgstr "Pulizia"
+
+#: frontend/pisa_progress_window.cc:62 frontend/pisa_view_manager.cc:774
+msgid " Finish "
+msgstr " Completare "
+
+#: frontend/pisa_progress_window.cc:171
+msgid "Scanner is warming up. Please wait..."
+msgstr "Lo Scanner non è pronto. Attendere prego..."
+
+#: frontend/pisa_progress_window.cc:174
+msgid "Pre-scanning in Progress"
+msgstr "Anteprima in corso"
+
+#: frontend/pisa_progress_window.cc:177
+msgid "Scanning in Progress"
+msgstr "Scansione in corso"
+
+#: frontend/pisa_progress_window.cc:180
+msgid ""
+"Starting a sequence of scans.\n"
+"Press to scanner's Start button to start each scan."
+msgstr ""
+"Inizio di una serie di scansione.\n"
+"Premere il tasto Avvio sullo scanner, per avviare ciascuna scansione."
+
+#: frontend/pisa_scan_selector.cc:103
+msgid "Scan Selector Dialog"
+msgstr ""
+
+#: frontend/pisa_scan_selector.cc:163
+msgid " Update "
+msgstr " Aggiorna "
+
+#: frontend/pisa_view_manager.cc:762
+msgid " Continue "
+msgstr " Continua "
+
+#~ msgid "Adjust"
+#~ msgstr "Regolazioni"
diff --git a/po/ja.gmo b/po/ja.gmo
new file mode 100644
index 0000000..928c607
--- /dev/null
+++ b/po/ja.gmo
Binary files differ
diff --git a/po/ja.po b/po/ja.po
new file mode 100644
index 0000000..b9cd05b
--- /dev/null
+++ b/po/ja.po
@@ -0,0 +1,460 @@
+# ja.po -- Japanese translations for `iscan' messages
+# Copyright (C) 2002, 2003, 2012 SEIKO EPSON CORPORATION
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: iscan 1.9.0\n"
+"Report-Msgid-Bugs-To: linux-printer@epson.jp\n"
+"POT-Creation-Date: 2019-08-21 08:06+0900\n"
+"PO-Revision-Date: 2011-05-30 15:53+0900\n"
+"Last-Translator: SEIKO EPSON CORPORATION\n"
+"Language-Team: Japanese\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8-bit\n"
+"Language: \n"
+
+#: frontend/file-selector.cc:465
+msgid "Overwrite"
+msgstr "上書き"
+
+#: frontend/file-selector.cc:465
+msgid "Cancel"
+msgstr "キャンセル"
+
+#: frontend/file-selector.cc:746
+msgid "Save Options"
+msgstr "保存オプション"
+
+#: frontend/file-selector.cc:765
+msgid "Create file with all pages"
+msgstr "全ページを1ファイルに保存"
+
+#: frontend/file-selector.cc:768
+msgid "Binding Position (for Double-Sided Scanning)"
+msgstr "とじる位置(両面スキャン時)"
+
+#: frontend/file-selector.cc:769
+msgid "Left"
+msgstr "左"
+
+#: frontend/file-selector.cc:771
+msgid "Top"
+msgstr "上"
+
+#: frontend/file-selector.cc:776
+msgid "Determine File Type:"
+msgstr "ファイル形式の決定:"
+
+#: frontend/file-selector.cc:835
+msgid "Start filing at:"
+msgstr "開始番号"
+
+#: frontend/file-selector.cc:858
+msgid "Number of digits:"
+msgstr "桁数"
+
+#: frontend/pisa_aleart_dialog.cc:73 frontend/pisa_configuration.cc:204
+#: frontend/pisa_scan_selector.cc:199
+msgid " OK "
+msgstr " OK "
+
+#: frontend/pisa_configuration.cc:113 frontend/pisa_main_window.cc:849
+msgid "Configuration"
+msgstr "環境設定"
+
+#: frontend/pisa_configuration.cc:163
+msgid "Print Command"
+msgstr "印刷コマンド"
+
+#: frontend/pisa_configuration.cc:186
+msgid ""
+"In order to print, your print system must be able to handle the PNG file "
+"format directly. CUPS or Photo Image Print System (versions 1.3.1 or later) "
+"do this by default."
+msgstr ""
+"印刷を行うためには、PNGファイル形式を直接処理できる印刷システムが必要です。"
+"CUPSやPhoto Image Print System(バージョン1.3.1以降)はこのようなシステムで"
+"す。"
+
+#: frontend/pisa_configuration.cc:214 frontend/pisa_progress_window.cc:61
+#: frontend/pisa_scan_selector.cc:213 frontend/pisa_view_manager.cc:762
+msgid " Cancel "
+msgstr " キャンセル "
+
+#: frontend/pisa_error.cc:85
+msgid "Operation completed succesfully."
+msgstr ""
+
+#: frontend/pisa_error.cc:87
+msgid "Operation is not supported."
+msgstr ""
+
+#: frontend/pisa_error.cc:89
+msgid "Operation was cancelled."
+msgstr ""
+
+#: frontend/pisa_error.cc:91
+msgid "Device is busy---retry later."
+msgstr ""
+
+#: frontend/pisa_error.cc:93
+msgid "Data or argument is invalid."
+msgstr ""
+
+#: frontend/pisa_error.cc:95
+msgid "No more data available (end-of-file)."
+msgstr ""
+
+#: frontend/pisa_error.cc:97
+msgid ""
+"A paper jam occured. Open the Automatic Document Feeder and remove any "
+"paper."
+msgstr ""
+"ADFで原稿が詰まりました。カバーを開けて詰った原稿を取り除いてください。"
+
+#: frontend/pisa_error.cc:100
+msgid "Please load the document(s) into the Automatic Document Feeder."
+msgstr "ADFに原稿をセットしてください。"
+
+#: frontend/pisa_error.cc:103
+msgid ""
+"The automatic document feeder or scanner unit is open.\n"
+"Please close it."
+msgstr ""
+"ADFまたはスキャナユニットが開いています。ADFまたはスキャナユニットを閉じてく"
+"ださい。"
+
+#: frontend/pisa_error.cc:106
+msgid "Error during device I/O."
+msgstr ""
+
+#: frontend/pisa_error.cc:108
+msgid "Out of memory."
+msgstr ""
+
+#: frontend/pisa_error.cc:110
+msgid "Access to resource has been denied."
+msgstr ""
+
+#: frontend/pisa_error.cc:113
+msgid "There is not enough disk space for operation"
+msgstr "操作に必要なディスク領域が足りません"
+
+#: frontend/pisa_error.cc:116
+msgid ""
+"Could not send command to scanner.\n"
+"Check the scanner's status."
+msgstr "スキャナにコマンドを転送できません。スキャナの状態を確認してください。"
+
+#: frontend/pisa_error.cc:120
+msgid "Scanner model not supported"
+msgstr "このスキャナはサポートしていません"
+
+#: frontend/pisa_error.cc:123
+msgid ""
+"Selected area is too large for this resolution.\n"
+"Reduce the selected area or resolution."
+msgstr "指定された範囲が広すぎます。範囲を縮小するか、解像度を下げてください。"
+
+#: frontend/pisa_error.cc:127 frontend/pisa_error.cc:130
+msgid "Could not create file"
+msgstr "ファイルが作成できません"
+
+#: frontend/pisa_error.cc:133
+msgid ""
+"A file with the same name already exists.\n"
+"Click \"Overwrite\" to replace the file or \"Cancel\" if you want to use "
+"another file name."
+msgstr ""
+"同じ名前のファイルが既に存在しています。\n"
+"上書き保存をする場合は、[上書き] ボタンをクリックしてください。\n"
+"ファイル名を変更する場合は、[キャンセル]ボタンをクリックしてください。"
+
+#: frontend/pisa_error.cc:138
+msgid ""
+"The Image Type setting you selected cannot be used with this resolution.\n"
+"Reduce the Resolution or Scale setting."
+msgstr ""
+"設定したイメージタイプで取り込むには、解像度またはズームを下げてください。"
+
+#: frontend/pisa_error.cc:143
+msgid "Tray cover is closed. Please open the tray cover and then scan again."
+msgstr "トレイカバーが閉じています。トレイカバーを開けてください。"
+
+#: frontend/pisa_error.cc:147
+msgid ""
+"A multi page feed occurred in the auto document feeder.\n"
+"Open the cover, remove the documents, and then try again. If documents "
+"remain on the tray, remove them and then reload them."
+msgstr ""
+"オートドキュメントフィーダで用紙が重なって給紙されました。\n"
+"カバーを開けて原稿を取り除き、再度スキャンしてください。トレーに原稿があると"
+"きは、カバーを閉じてから原稿をセットし直してください。"
+
+#: frontend/pisa_error.cc:156
+msgid "Unexpected error occurred"
+msgstr "予期せぬエラーが発生しました"
+
+#: frontend/pisa_gamma_correction.cc:482
+msgid " Reset "
+msgstr " リセット "
+
+#: frontend/pisa_image_controls.cc:253
+msgid "Gamma:"
+msgstr "ガンマ:"
+
+#: frontend/pisa_image_controls.cc:268
+msgid "Highlight:"
+msgstr "ハイライト:"
+
+#: frontend/pisa_image_controls.cc:283
+msgid "Shadow:"
+msgstr "シャドウ:"
+
+#: frontend/pisa_image_controls.cc:298
+msgid "Threshold:"
+msgstr "しきい値:"
+
+#: frontend/pisa_image_controls.cc:331
+msgid "Brightness:"
+msgstr "明るさ:"
+
+#: frontend/pisa_image_controls.cc:354
+msgid "Contrast:"
+msgstr "コントラスト:"
+
+#: frontend/pisa_main_window.cc:90
+msgid "File"
+msgstr "ファイル"
+
+#: frontend/pisa_main_window.cc:91
+msgid "Printer"
+msgstr "プリンタ"
+
+#: frontend/pisa_main_window.cc:104
+msgid "Flatbed"
+msgstr "原稿台"
+
+#: frontend/pisa_main_window.cc:105
+msgid "TPU - Negative film"
+msgstr "透過原稿ユニット−ネガフィルム"
+
+#: frontend/pisa_main_window.cc:106
+msgid "TPU - Positive film"
+msgstr "透過原稿ユニット−ポジフィルム"
+
+#: frontend/pisa_main_window.cc:107
+msgid "ADF - Single-sided"
+msgstr "ADF-片面"
+
+#: frontend/pisa_main_window.cc:108
+msgid "ADF - Double-sided"
+msgstr "ADF-両面"
+
+#: frontend/pisa_main_window.cc:121
+msgid "Color Photo"
+msgstr "カラー写真"
+
+#: frontend/pisa_main_window.cc:122
+msgid "Color Document"
+msgstr "カラー書類"
+
+#: frontend/pisa_main_window.cc:123
+msgid "Black & White Photo"
+msgstr "白黒写真"
+
+#: frontend/pisa_main_window.cc:124
+msgid "Black & White Document"
+msgstr "白黒書類"
+
+#: frontend/pisa_main_window.cc:125
+msgid "Line Art"
+msgstr "線画"
+
+#: frontend/pisa_main_window.cc:169
+msgid "inches"
+msgstr "インチ"
+
+#: frontend/pisa_main_window.cc:170
+msgid "pixels"
+msgstr "ピクセル"
+
+#: frontend/pisa_main_window.cc:171
+msgid "cm"
+msgstr "cm"
+
+#: frontend/pisa_main_window.cc:192
+msgid "Off"
+msgstr "なし"
+
+#: frontend/pisa_main_window.cc:193
+msgid "Standard"
+msgstr "標準"
+
+#: frontend/pisa_main_window.cc:194
+msgid "Thin"
+msgstr "薄い"
+
+#: frontend/pisa_main_window.cc:299
+msgid "Calibration is failed."
+msgstr "キャリブレーションに失敗しました。"
+
+#: frontend/pisa_main_window.cc:325
+msgid "Cleaning is failed."
+msgstr "クリーニングに失敗しました。"
+
+#: frontend/pisa_main_window.cc:831
+msgid "Scan to File"
+msgstr "スキャンして保存"
+
+#: frontend/pisa_main_window.cc:837
+msgid "Scan"
+msgstr "取り込み"
+
+#: frontend/pisa_main_window.cc:903
+msgid "Close"
+msgstr "閉じる"
+
+#: frontend/pisa_main_window.cc:922
+msgid "Preview"
+msgstr "プレビュー"
+
+#: frontend/pisa_main_window.cc:923
+msgid "Zoom"
+msgstr "ズーム"
+
+#: frontend/pisa_main_window.cc:924
+msgid "Auto Exposure"
+msgstr "自動露光"
+
+#: frontend/pisa_main_window.cc:927
+msgid "enable Start button"
+msgstr "スキャナビボタン有効"
+
+#: frontend/pisa_main_window.cc:928
+msgid "Speed priority scanning"
+msgstr "速度優先"
+
+#: frontend/pisa_main_window.cc:930
+msgid "Document Size - Auto Detect"
+msgstr "原稿サイズ - 自動検知"
+
+#: frontend/pisa_main_window.cc:932
+msgid "Correct Document Skew"
+msgstr "書類の傾き補正"
+
+#: frontend/pisa_main_window.cc:937
+msgid "Unsharp mask"
+msgstr "アンシャープマスク"
+
+#: frontend/pisa_main_window.cc:941
+msgid "Detect Double Feed (Paper Thickness)"
+msgstr "重送検知(用紙厚設定)"
+
+#: frontend/pisa_main_window.cc:1027
+msgid "Scan to Print"
+msgstr "スキャンして印刷"
+
+#: frontend/pisa_main_window.cc:1073
+msgid "Destination:"
+msgstr "出力先:"
+
+#: frontend/pisa_main_window.cc:1152 frontend/pisa_scan_selector.cc:142
+msgid "Scanner:"
+msgstr "スキャナ"
+
+#: frontend/pisa_main_window.cc:1175
+msgid "Document"
+msgstr "原稿"
+
+#: frontend/pisa_main_window.cc:1186
+msgid "Image Controls"
+msgstr "イメージ制御"
+
+#: frontend/pisa_main_window.cc:1195
+msgid "Tone Correction"
+msgstr "濃度補正"
+
+#: frontend/pisa_main_window.cc:1233
+msgid "Document Source:"
+msgstr "原稿種:"
+
+#: frontend/pisa_main_window.cc:1266
+msgid "Image Type:"
+msgstr "イメージタイプ:"
+
+#: frontend/pisa_main_window.cc:1278
+msgid "Resolution:"
+msgstr "解像度:"
+
+#: frontend/pisa_main_window.cc:1327
+msgid "Target"
+msgstr "出力サイズ"
+
+#: frontend/pisa_main_window.cc:1338
+msgid "W:"
+msgstr "幅:"
+
+#: frontend/pisa_main_window.cc:1350
+msgid "H:"
+msgstr "高さ:"
+
+#: frontend/pisa_main_window.cc:1375
+msgid "Scale"
+msgstr "ズーム"
+
+#: frontend/pisa_main_window.cc:1399
+msgid "Focus"
+msgstr "焦点調整"
+
+#: frontend/pisa_main_window.cc:1429
+msgid "Options"
+msgstr "オプション"
+
+#: frontend/pisa_main_window.cc:1720
+msgid "Calibration"
+msgstr "キャリブレーション"
+
+#: frontend/pisa_main_window.cc:1731
+msgid "Cleaning"
+msgstr "クリーニング"
+
+#: frontend/pisa_progress_window.cc:62 frontend/pisa_view_manager.cc:774
+msgid " Finish "
+msgstr " 終了 "
+
+#: frontend/pisa_progress_window.cc:171
+msgid "Scanner is warming up. Please wait..."
+msgstr "スキャナがウォームアップ中です。しばらくお待ちください。"
+
+#: frontend/pisa_progress_window.cc:174
+msgid "Pre-scanning in Progress"
+msgstr "プレビュー中"
+
+#: frontend/pisa_progress_window.cc:177
+msgid "Scanning in Progress"
+msgstr "取り込み中"
+
+#: frontend/pisa_progress_window.cc:180
+msgid ""
+"Starting a sequence of scans.\n"
+"Press to scanner's Start button to start each scan."
+msgstr ""
+"連続スキャン中です。\n"
+"スキャナビボタンを押すと、スキャンが始まります"
+
+#: frontend/pisa_scan_selector.cc:103
+msgid "Scan Selector Dialog"
+msgstr ""
+
+#: frontend/pisa_scan_selector.cc:163
+msgid " Update "
+msgstr " 更新 "
+
+#: frontend/pisa_view_manager.cc:762
+msgid " Continue "
+msgstr " 継続 "
+
+#~ msgid "Adjust"
+#~ msgstr "調整"
diff --git a/po/ko.gmo b/po/ko.gmo
new file mode 100644
index 0000000..bd5f0a0
--- /dev/null
+++ b/po/ko.gmo
Binary files differ
diff --git a/po/ko.po b/po/ko.po
new file mode 100644
index 0000000..57803ee
--- /dev/null
+++ b/po/ko.po
@@ -0,0 +1,460 @@
+# ko.po - Korean (Hangul) message strings for Image Scan!
+# Copyright (C) 2003, 2012 SEIKO EPSON CORPORATION
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: iscan 1.9.0\n"
+"Report-Msgid-Bugs-To: linux-printer@epson.jp\n"
+"POT-Creation-Date: 2019-08-21 08:06+0900\n"
+"PO-Revision-Date: 2011-05-30 16:12+0900\n"
+"Last-Translator: SEIKO EPSON CORPORATION\n"
+"Language-Team: Korean\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: \n"
+
+#: frontend/file-selector.cc:465
+msgid "Overwrite"
+msgstr "덮어쓰기"
+
+#: frontend/file-selector.cc:465
+msgid "Cancel"
+msgstr "취소"
+
+#: frontend/file-selector.cc:746
+msgid "Save Options"
+msgstr "옵션 저장"
+
+#: frontend/file-selector.cc:765
+msgid "Create file with all pages"
+msgstr ""
+
+#: frontend/file-selector.cc:768
+msgid "Binding Position (for Double-Sided Scanning)"
+msgstr "철하는 위치(양면 스캔시)"
+
+#: frontend/file-selector.cc:769
+msgid "Left"
+msgstr "왼쪽"
+
+#: frontend/file-selector.cc:771
+msgid "Top"
+msgstr "위쪽"
+
+#: frontend/file-selector.cc:776
+msgid "Determine File Type:"
+msgstr "파일 형식 결정"
+
+#: frontend/file-selector.cc:835
+msgid "Start filing at:"
+msgstr "정리 시작"
+
+#: frontend/file-selector.cc:858
+msgid "Number of digits:"
+msgstr "숫자 번호"
+
+#: frontend/pisa_aleart_dialog.cc:73 frontend/pisa_configuration.cc:204
+#: frontend/pisa_scan_selector.cc:199
+msgid " OK "
+msgstr " 확인 "
+
+#: frontend/pisa_configuration.cc:113 frontend/pisa_main_window.cc:849
+msgid "Configuration"
+msgstr "환경 설정"
+
+#: frontend/pisa_configuration.cc:163
+msgid "Print Command"
+msgstr "인쇄 명령어"
+
+#: frontend/pisa_configuration.cc:186
+msgid ""
+"In order to print, your print system must be able to handle the PNG file "
+"format directly. CUPS or Photo Image Print System (versions 1.3.1 or later) "
+"do this by default."
+msgstr ""
+
+#: frontend/pisa_configuration.cc:214 frontend/pisa_progress_window.cc:61
+#: frontend/pisa_scan_selector.cc:213 frontend/pisa_view_manager.cc:762
+msgid " Cancel "
+msgstr " 취소 "
+
+#: frontend/pisa_error.cc:85
+msgid "Operation completed succesfully."
+msgstr ""
+
+#: frontend/pisa_error.cc:87
+msgid "Operation is not supported."
+msgstr ""
+
+#: frontend/pisa_error.cc:89
+msgid "Operation was cancelled."
+msgstr ""
+
+#: frontend/pisa_error.cc:91
+msgid "Device is busy---retry later."
+msgstr ""
+
+#: frontend/pisa_error.cc:93
+msgid "Data or argument is invalid."
+msgstr ""
+
+#: frontend/pisa_error.cc:95
+msgid "No more data available (end-of-file)."
+msgstr ""
+
+#: frontend/pisa_error.cc:97
+msgid ""
+"A paper jam occured. Open the Automatic Document Feeder and remove any "
+"paper."
+msgstr ""
+"용지 걸림이 발생하였습니다. 자동 문서 공급 장치를 열고 걸려있는 용지를 제거"
+"하십시오."
+
+#: frontend/pisa_error.cc:100
+msgid "Please load the document(s) into the Automatic Document Feeder."
+msgstr "자동 문서 공급 장치에 문서를 올려주십시오."
+
+#: frontend/pisa_error.cc:103
+msgid ""
+"The automatic document feeder or scanner unit is open.\n"
+"Please close it."
+msgstr ""
+"자동 문서 공급 장치나 스캐너 장치가 열렸습니다.\n"
+"장치를 닫아주십시오."
+
+#: frontend/pisa_error.cc:106
+msgid "Error during device I/O."
+msgstr ""
+
+#: frontend/pisa_error.cc:108
+msgid "Out of memory."
+msgstr ""
+
+#: frontend/pisa_error.cc:110
+msgid "Access to resource has been denied."
+msgstr ""
+
+#: frontend/pisa_error.cc:113
+msgid "There is not enough disk space for operation"
+msgstr "스캐너가 동작하기에는 여유 공간이 부족합니다."
+
+#: frontend/pisa_error.cc:116
+msgid ""
+"Could not send command to scanner.\n"
+"Check the scanner's status."
+msgstr ""
+"스캐너로 명령을 보낼 수 없습니다.\n"
+"스캐너의 상태를 확인하십시오."
+
+#: frontend/pisa_error.cc:120
+msgid "Scanner model not supported"
+msgstr "지원하지 않는 스캐너 모델입니다."
+
+#: frontend/pisa_error.cc:123
+msgid ""
+"Selected area is too large for this resolution.\n"
+"Reduce the selected area or resolution."
+msgstr ""
+"이 해상도에 비해서 선택되어진 영역이 너무 큽니다.\n"
+"선택되어진 영역 또는 해상도를 줄여주십시오."
+
+#: frontend/pisa_error.cc:127 frontend/pisa_error.cc:130
+msgid "Could not create file"
+msgstr "파일을 만들 수 없습니다."
+
+#: frontend/pisa_error.cc:133
+msgid ""
+"A file with the same name already exists.\n"
+"Click \"Overwrite\" to replace the file or \"Cancel\" if you want to use "
+"another file name."
+msgstr ""
+"같은 이름의 파일이 있습니다.\n"
+"파일을 덮어쓰기 하려면 \"덮어쓰기\"를, 다른 파일 이름을 사용할 경우는 \"취소"
+"\"를 클릭합니다."
+
+#: frontend/pisa_error.cc:138
+msgid ""
+"The Image Type setting you selected cannot be used with this resolution.\n"
+"Reduce the Resolution or Scale setting."
+msgstr ""
+"선택한 이미지 형식 설정은 이 해상도에서 사용할 수 없습니다. 해상도나 비율을 "
+"줄이십시오."
+
+#: frontend/pisa_error.cc:143
+msgid "Tray cover is closed. Please open the tray cover and then scan again."
+msgstr ""
+
+#: frontend/pisa_error.cc:147
+msgid ""
+"A multi page feed occurred in the auto document feeder.\n"
+"Open the cover, remove the documents, and then try again. If documents "
+"remain on the tray, remove them and then reload them."
+msgstr ""
+
+#: frontend/pisa_error.cc:156
+msgid "Unexpected error occurred"
+msgstr "예기치 않은 오류가 발생하였습니다."
+
+#: frontend/pisa_gamma_correction.cc:482
+msgid " Reset "
+msgstr " 초기화 "
+
+#: frontend/pisa_image_controls.cc:253
+msgid "Gamma:"
+msgstr "감마:"
+
+#: frontend/pisa_image_controls.cc:268
+msgid "Highlight:"
+msgstr "밝은 영역:"
+
+#: frontend/pisa_image_controls.cc:283
+msgid "Shadow:"
+msgstr "어두운 영역:"
+
+#: frontend/pisa_image_controls.cc:298
+msgid "Threshold:"
+msgstr "흑백 경계값:"
+
+#: frontend/pisa_image_controls.cc:331
+msgid "Brightness:"
+msgstr "명도:"
+
+#: frontend/pisa_image_controls.cc:354
+msgid "Contrast:"
+msgstr "대비:"
+
+#: frontend/pisa_main_window.cc:90
+msgid "File"
+msgstr "파일"
+
+#: frontend/pisa_main_window.cc:91
+msgid "Printer"
+msgstr "프린터"
+
+#: frontend/pisa_main_window.cc:104
+msgid "Flatbed"
+msgstr "평판"
+
+#: frontend/pisa_main_window.cc:105
+msgid "TPU - Negative film"
+msgstr "TPU - 네거티브 필름"
+
+#: frontend/pisa_main_window.cc:106
+msgid "TPU - Positive film"
+msgstr "TPU - 포지티브 필름"
+
+#: frontend/pisa_main_window.cc:107
+msgid "ADF - Single-sided"
+msgstr "ADF-단면"
+
+#: frontend/pisa_main_window.cc:108
+msgid "ADF - Double-sided"
+msgstr "ADF-양면"
+
+#: frontend/pisa_main_window.cc:121
+msgid "Color Photo"
+msgstr "컬러 사진"
+
+#: frontend/pisa_main_window.cc:122
+msgid "Color Document"
+msgstr "컬러 문서"
+
+#: frontend/pisa_main_window.cc:123
+msgid "Black & White Photo"
+msgstr "흑백 사진"
+
+#: frontend/pisa_main_window.cc:124
+msgid "Black & White Document"
+msgstr "흑백 문서"
+
+#: frontend/pisa_main_window.cc:125
+msgid "Line Art"
+msgstr "라인 아트"
+
+#: frontend/pisa_main_window.cc:169
+msgid "inches"
+msgstr "inches"
+
+#: frontend/pisa_main_window.cc:170
+msgid "pixels"
+msgstr "pixels"
+
+#: frontend/pisa_main_window.cc:171
+msgid "cm"
+msgstr "cm"
+
+#: frontend/pisa_main_window.cc:192
+msgid "Off"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:193
+msgid "Standard"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:194
+msgid "Thin"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:299
+msgid "Calibration is failed."
+msgstr "캘리브레이션을 완료하지 못했습니다."
+
+#: frontend/pisa_main_window.cc:325
+msgid "Cleaning is failed."
+msgstr "청소를 완료하지 못했습니다."
+
+#: frontend/pisa_main_window.cc:831
+msgid "Scan to File"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:837
+msgid "Scan"
+msgstr "스캔"
+
+#: frontend/pisa_main_window.cc:903
+msgid "Close"
+msgstr "닫기"
+
+#: frontend/pisa_main_window.cc:922
+msgid "Preview"
+msgstr "미리보기"
+
+#: frontend/pisa_main_window.cc:923
+msgid "Zoom"
+msgstr "확대"
+
+#: frontend/pisa_main_window.cc:924
+msgid "Auto Exposure"
+msgstr "자동 노출"
+
+#: frontend/pisa_main_window.cc:927
+msgid "enable Start button"
+msgstr "시작 버튼 사용"
+
+#: frontend/pisa_main_window.cc:928
+msgid "Speed priority scanning"
+msgstr "속도우선 스캔"
+
+#: frontend/pisa_main_window.cc:930
+msgid "Document Size - Auto Detect"
+msgstr "문서크기-자동감지"
+
+#: frontend/pisa_main_window.cc:932
+msgid "Correct Document Skew"
+msgstr "기울어진 문서 보정"
+
+#: frontend/pisa_main_window.cc:937
+msgid "Unsharp mask"
+msgstr "흐린 영역 보정"
+
+#: frontend/pisa_main_window.cc:941
+msgid "Detect Double Feed (Paper Thickness)"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1027
+msgid "Scan to Print"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1073
+msgid "Destination:"
+msgstr "출력 대상:"
+
+#: frontend/pisa_main_window.cc:1152 frontend/pisa_scan_selector.cc:142
+msgid "Scanner:"
+msgstr "스캐너"
+
+#: frontend/pisa_main_window.cc:1175
+msgid "Document"
+msgstr "문서"
+
+#: frontend/pisa_main_window.cc:1186
+msgid "Image Controls"
+msgstr "이미지 조정"
+
+#: frontend/pisa_main_window.cc:1195
+msgid "Tone Correction"
+msgstr "톤 보정"
+
+#: frontend/pisa_main_window.cc:1233
+msgid "Document Source:"
+msgstr "문서 공급 방법:"
+
+#: frontend/pisa_main_window.cc:1266
+msgid "Image Type:"
+msgstr "이미지 형식:"
+
+#: frontend/pisa_main_window.cc:1278
+msgid "Resolution:"
+msgstr "해상도:"
+
+#: frontend/pisa_main_window.cc:1327
+msgid "Target"
+msgstr "대상 크기"
+
+#: frontend/pisa_main_window.cc:1338
+msgid "W:"
+msgstr "W:"
+
+#: frontend/pisa_main_window.cc:1350
+msgid "H:"
+msgstr "H:"
+
+#: frontend/pisa_main_window.cc:1375
+msgid "Scale"
+msgstr "비율"
+
+#: frontend/pisa_main_window.cc:1399
+msgid "Focus"
+msgstr "초점"
+
+#: frontend/pisa_main_window.cc:1429
+msgid "Options"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1720
+msgid "Calibration"
+msgstr "캘리브레이션"
+
+#: frontend/pisa_main_window.cc:1731
+msgid "Cleaning"
+msgstr "청소"
+
+#: frontend/pisa_progress_window.cc:62 frontend/pisa_view_manager.cc:774
+msgid " Finish "
+msgstr " 마침 "
+
+#: frontend/pisa_progress_window.cc:171
+msgid "Scanner is warming up. Please wait..."
+msgstr "스캐너가 준비중입니다. 잠시만 기다려 주십시오."
+
+#: frontend/pisa_progress_window.cc:174
+msgid "Pre-scanning in Progress"
+msgstr "미리보기 진행 중"
+
+#: frontend/pisa_progress_window.cc:177
+msgid "Scanning in Progress"
+msgstr "스캔 진행 중"
+
+#: frontend/pisa_progress_window.cc:180
+msgid ""
+"Starting a sequence of scans.\n"
+"Press to scanner's Start button to start each scan."
+msgstr ""
+"연속 스캔을 시작합니다. \n"
+"스캔을 시작하려면 스캐너의 시작 버튼을 누르십시오."
+
+#: frontend/pisa_scan_selector.cc:103
+msgid "Scan Selector Dialog"
+msgstr ""
+
+#: frontend/pisa_scan_selector.cc:163
+msgid " Update "
+msgstr " 갱신 "
+
+#: frontend/pisa_view_manager.cc:762
+msgid " Continue "
+msgstr " 계속 "
+
+#~ msgid "Adjust"
+#~ msgstr "조정"
diff --git a/po/nl.gmo b/po/nl.gmo
new file mode 100644
index 0000000..d95e6f5
--- /dev/null
+++ b/po/nl.gmo
Binary files differ
diff --git a/po/nl.po b/po/nl.po
new file mode 100644
index 0000000..7a4d4d5
--- /dev/null
+++ b/po/nl.po
@@ -0,0 +1,467 @@
+# nl.po -- Dutch translations for `iscan' messages
+# Copyright (C) 2003, 2012 SEIKO EPSON CORPORATION
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: iscan 1.9.0\n"
+"Report-Msgid-Bugs-To: linux-printer@epson.jp\n"
+"POT-Creation-Date: 2019-08-21 08:06+0900\n"
+"PO-Revision-Date: 2011-05-30 15:56+0900\n"
+"Last-Translator: SEIKO EPSON CORPORATION\n"
+"Language-Team: Dutch\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: \n"
+
+#: frontend/file-selector.cc:465
+msgid "Overwrite"
+msgstr "Overschrijven"
+
+#: frontend/file-selector.cc:465
+msgid "Cancel"
+msgstr "Annuleren"
+
+#: frontend/file-selector.cc:746
+msgid "Save Options"
+msgstr "Bewaaropties"
+
+#: frontend/file-selector.cc:765
+msgid "Create file with all pages"
+msgstr "Alle pagina's in 1 bestand"
+
+#: frontend/file-selector.cc:768
+msgid "Binding Position (for Double-Sided Scanning)"
+msgstr "Bindkant (dubbelzijdig scannen)"
+
+#: frontend/file-selector.cc:769
+msgid "Left"
+msgstr "Links"
+
+#: frontend/file-selector.cc:771
+msgid "Top"
+msgstr "Boven"
+
+#: frontend/file-selector.cc:776
+msgid "Determine File Type:"
+msgstr "Bepaal bestandstype:"
+
+#: frontend/file-selector.cc:835
+msgid "Start filing at:"
+msgstr "Begin met nummer"
+
+#: frontend/file-selector.cc:858
+msgid "Number of digits:"
+msgstr "Aantal cijfers"
+
+#: frontend/pisa_aleart_dialog.cc:73 frontend/pisa_configuration.cc:204
+#: frontend/pisa_scan_selector.cc:199
+msgid " OK "
+msgstr " OK "
+
+#: frontend/pisa_configuration.cc:113 frontend/pisa_main_window.cc:849
+msgid "Configuration"
+msgstr "Configuratie"
+
+#: frontend/pisa_configuration.cc:163
+msgid "Print Command"
+msgstr "Afdrukcommando"
+
+#: frontend/pisa_configuration.cc:186
+msgid ""
+"In order to print, your print system must be able to handle the PNG file "
+"format directly. CUPS or Photo Image Print System (versions 1.3.1 or later) "
+"do this by default."
+msgstr ""
+
+#: frontend/pisa_configuration.cc:214 frontend/pisa_progress_window.cc:61
+#: frontend/pisa_scan_selector.cc:213 frontend/pisa_view_manager.cc:762
+msgid " Cancel "
+msgstr " Annuleren "
+
+#: frontend/pisa_error.cc:85
+msgid "Operation completed succesfully."
+msgstr ""
+
+#: frontend/pisa_error.cc:87
+msgid "Operation is not supported."
+msgstr ""
+
+#: frontend/pisa_error.cc:89
+msgid "Operation was cancelled."
+msgstr ""
+
+#: frontend/pisa_error.cc:91
+msgid "Device is busy---retry later."
+msgstr ""
+
+#: frontend/pisa_error.cc:93
+msgid "Data or argument is invalid."
+msgstr ""
+
+#: frontend/pisa_error.cc:95
+msgid "No more data available (end-of-file)."
+msgstr ""
+
+#: frontend/pisa_error.cc:97
+msgid ""
+"A paper jam occured. Open the Automatic Document Feeder and remove any "
+"paper."
+msgstr ""
+"Er heeft zich een papierstoring voorgedaan.\n"
+"Open de automatische documententoevoerder en verwijder het papier."
+
+#: frontend/pisa_error.cc:100
+msgid "Please load the document(s) into the Automatic Document Feeder."
+msgstr "Laad document(en) in de automatische documententoevoerder."
+
+#: frontend/pisa_error.cc:103
+msgid ""
+"The automatic document feeder or scanner unit is open.\n"
+"Please close it."
+msgstr ""
+"De automatische documententoevoer of scannereenheid is open. Sluit de "
+"toevoer of eenheid."
+
+#: frontend/pisa_error.cc:106
+msgid "Error during device I/O."
+msgstr ""
+
+#: frontend/pisa_error.cc:108
+msgid "Out of memory."
+msgstr ""
+
+#: frontend/pisa_error.cc:110
+msgid "Access to resource has been denied."
+msgstr ""
+
+#: frontend/pisa_error.cc:113
+msgid "There is not enough disk space for operation"
+msgstr "Er is niet genoeg schijfruimte voor uitvoering"
+
+#: frontend/pisa_error.cc:116
+msgid ""
+"Could not send command to scanner.\n"
+"Check the scanner's status."
+msgstr ""
+"Commando naar scanner sturen is mislukt. Controleer de status van de scanner."
+
+#: frontend/pisa_error.cc:120
+msgid "Scanner model not supported"
+msgstr "Scannermodel wordt niet ondersteund"
+
+#: frontend/pisa_error.cc:123
+msgid ""
+"Selected area is too large for this resolution.\n"
+"Reduce the selected area or resolution."
+msgstr ""
+"Het geselecteerde gebied is te groot voor deze resolutie.\n"
+"Verlaag de resolutie of de schaalinstelling, of verklein het geselecteerde "
+"gebied."
+
+#: frontend/pisa_error.cc:127 frontend/pisa_error.cc:130
+msgid "Could not create file"
+msgstr "Kon geen bestand creëren"
+
+#: frontend/pisa_error.cc:133
+msgid ""
+"A file with the same name already exists.\n"
+"Click \"Overwrite\" to replace the file or \"Cancel\" if you want to use "
+"another file name."
+msgstr ""
+"Er bestaat al een bestand met deze naam.\n"
+"Klik op “Overschrijven” om het bestaande bestand te vervangen, of klik op "
+"“Annuleren” als u een andere bestandsnaam wilt gebruiken."
+
+#: frontend/pisa_error.cc:138
+msgid ""
+"The Image Type setting you selected cannot be used with this resolution.\n"
+"Reduce the Resolution or Scale setting."
+msgstr ""
+"De geselecteerde instelling voor het beeldtype kan niet gebruikt worden met "
+"deze resolutie. Verlaag de resolutie of de schaalinstelling."
+
+#: frontend/pisa_error.cc:143
+msgid "Tray cover is closed. Please open the tray cover and then scan again."
+msgstr ""
+"Het deksel van de toevoerder is gesloten. Open het deksel en probeer het "
+"opnieuw."
+
+#: frontend/pisa_error.cc:147
+msgid ""
+"A multi page feed occurred in the auto document feeder.\n"
+"Open the cover, remove the documents, and then try again. If documents "
+"remain on the tray, remove them and then reload them."
+msgstr ""
+"Fout in automatische documententoevoerder: er werden meerdere pagina's "
+"tegelijk ingevoerd.\n"
+"Open het deksel, verwijder de pagina's en probeer het opnieuw. Als er zich "
+"nog pagina's in de toevoerder bevinden, verwijdert u deze en laadt u ze "
+"opnieuw."
+
+#: frontend/pisa_error.cc:156
+msgid "Unexpected error occurred"
+msgstr "Er heeft zich een onverwachte fout voorgedaan"
+
+#: frontend/pisa_gamma_correction.cc:482
+msgid " Reset "
+msgstr " Resetten "
+
+#: frontend/pisa_image_controls.cc:253
+msgid "Gamma:"
+msgstr "Gamma:"
+
+#: frontend/pisa_image_controls.cc:268
+msgid "Highlight:"
+msgstr "Lichten:"
+
+#: frontend/pisa_image_controls.cc:283
+msgid "Shadow:"
+msgstr "Schaduwen:"
+
+#: frontend/pisa_image_controls.cc:298
+msgid "Threshold:"
+msgstr "Drempelwaarde:"
+
+#: frontend/pisa_image_controls.cc:331
+msgid "Brightness:"
+msgstr "Helderheid:"
+
+#: frontend/pisa_image_controls.cc:354
+msgid "Contrast:"
+msgstr "Contrast:"
+
+#: frontend/pisa_main_window.cc:90
+msgid "File"
+msgstr "Bestand"
+
+#: frontend/pisa_main_window.cc:91
+msgid "Printer"
+msgstr "Printer"
+
+#: frontend/pisa_main_window.cc:104
+msgid "Flatbed"
+msgstr "Glasplaat"
+
+#: frontend/pisa_main_window.cc:105
+msgid "TPU - Negative film"
+msgstr "Transp-eenh. v. negatieven"
+
+#: frontend/pisa_main_window.cc:106
+msgid "TPU - Positive film"
+msgstr "Transp-eenh. v. positieven"
+
+#: frontend/pisa_main_window.cc:107
+msgid "ADF - Single-sided"
+msgstr "ADF - Enkelzijdig"
+
+#: frontend/pisa_main_window.cc:108
+msgid "ADF - Double-sided"
+msgstr "ADF - Dubbelzijdig"
+
+#: frontend/pisa_main_window.cc:121
+msgid "Color Photo"
+msgstr "Kleurenfoto"
+
+#: frontend/pisa_main_window.cc:122
+msgid "Color Document"
+msgstr "Kleurendocument"
+
+#: frontend/pisa_main_window.cc:123
+msgid "Black & White Photo"
+msgstr "Zwart-witfoto"
+
+#: frontend/pisa_main_window.cc:124
+msgid "Black & White Document"
+msgstr "Zwart-witdocument"
+
+#: frontend/pisa_main_window.cc:125
+msgid "Line Art"
+msgstr "Lijntekeningen"
+
+#: frontend/pisa_main_window.cc:169
+msgid "inches"
+msgstr "inches"
+
+#: frontend/pisa_main_window.cc:170
+msgid "pixels"
+msgstr "pixels"
+
+#: frontend/pisa_main_window.cc:171
+msgid "cm"
+msgstr "cm"
+
+#: frontend/pisa_main_window.cc:192
+msgid "Off"
+msgstr "Uit"
+
+#: frontend/pisa_main_window.cc:193
+msgid "Standard"
+msgstr "Standaard"
+
+#: frontend/pisa_main_window.cc:194
+msgid "Thin"
+msgstr "Dun"
+
+#: frontend/pisa_main_window.cc:299
+msgid "Calibration is failed."
+msgstr "Kalibratie mislukt."
+
+#: frontend/pisa_main_window.cc:325
+msgid "Cleaning is failed."
+msgstr "Reiniging mislukt."
+
+#: frontend/pisa_main_window.cc:831
+msgid "Scan to File"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:837
+msgid "Scan"
+msgstr "Scannen"
+
+#: frontend/pisa_main_window.cc:903
+msgid "Close"
+msgstr "Sluiten"
+
+#: frontend/pisa_main_window.cc:922
+msgid "Preview"
+msgstr "Voorbeeldscan"
+
+#: frontend/pisa_main_window.cc:923
+msgid "Zoom"
+msgstr "Zoomen"
+
+#: frontend/pisa_main_window.cc:924
+msgid "Auto Exposure"
+msgstr "Automatische belichting"
+
+#: frontend/pisa_main_window.cc:927
+msgid "enable Start button"
+msgstr "Start-knop inschakelen"
+
+#: frontend/pisa_main_window.cc:928
+msgid "Speed priority scanning"
+msgstr "Snel scannen"
+
+#: frontend/pisa_main_window.cc:930
+msgid "Document Size - Auto Detect"
+msgstr "Documentformaat - Automatisch detecteren"
+
+#: frontend/pisa_main_window.cc:932
+msgid "Correct Document Skew"
+msgstr "Scheve documenten corrigeren"
+
+#: frontend/pisa_main_window.cc:937
+msgid "Unsharp mask"
+msgstr "Beeld verscherpen"
+
+#: frontend/pisa_main_window.cc:941
+msgid "Detect Double Feed (Paper Thickness)"
+msgstr "Dubbele invoer detecteren (Papierdikte)"
+
+#: frontend/pisa_main_window.cc:1027
+msgid "Scan to Print"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1073
+msgid "Destination:"
+msgstr "Bestemming:"
+
+#: frontend/pisa_main_window.cc:1152 frontend/pisa_scan_selector.cc:142
+msgid "Scanner:"
+msgstr "Scanner:"
+
+#: frontend/pisa_main_window.cc:1175
+msgid "Document"
+msgstr "Document"
+
+#: frontend/pisa_main_window.cc:1186
+msgid "Image Controls"
+msgstr "Beeldbewerking"
+
+#: frontend/pisa_main_window.cc:1195
+msgid "Tone Correction"
+msgstr "Tooncorrectie"
+
+#: frontend/pisa_main_window.cc:1233
+msgid "Document Source:"
+msgstr "Documentbron:"
+
+#: frontend/pisa_main_window.cc:1266
+msgid "Image Type:"
+msgstr "Beeldtype:"
+
+#: frontend/pisa_main_window.cc:1278
+msgid "Resolution:"
+msgstr "Resolutie:"
+
+#: frontend/pisa_main_window.cc:1327
+msgid "Target"
+msgstr "Doel"
+
+#: frontend/pisa_main_window.cc:1338
+msgid "W:"
+msgstr "B:"
+
+#: frontend/pisa_main_window.cc:1350
+msgid "H:"
+msgstr "H:"
+
+#: frontend/pisa_main_window.cc:1375
+msgid "Scale"
+msgstr "Schaal"
+
+#: frontend/pisa_main_window.cc:1399
+msgid "Focus"
+msgstr "Focus"
+
+#: frontend/pisa_main_window.cc:1429
+msgid "Options"
+msgstr "Opties"
+
+#: frontend/pisa_main_window.cc:1720
+msgid "Calibration"
+msgstr "Kalibratie"
+
+#: frontend/pisa_main_window.cc:1731
+msgid "Cleaning"
+msgstr "Reiniging"
+
+#: frontend/pisa_progress_window.cc:62 frontend/pisa_view_manager.cc:774
+msgid " Finish "
+msgstr " Einde "
+
+#: frontend/pisa_progress_window.cc:171
+msgid "Scanner is warming up. Please wait..."
+msgstr "De scanner is bezig met opwarmen. Een ogenblikje geduld."
+
+#: frontend/pisa_progress_window.cc:174
+msgid "Pre-scanning in Progress"
+msgstr "Voorbeeldscan wordt gemaakt"
+
+#: frontend/pisa_progress_window.cc:177
+msgid "Scanning in Progress"
+msgstr "Scan wordt uitgevoerd"
+
+#: frontend/pisa_progress_window.cc:180
+msgid ""
+"Starting a sequence of scans.\n"
+"Press to scanner's Start button to start each scan."
+msgstr ""
+"Een opeenvolging van scans starten. Druk op de Startknop van de scanner om "
+"elke scan te starten."
+
+#: frontend/pisa_scan_selector.cc:103
+msgid "Scan Selector Dialog"
+msgstr ""
+
+#: frontend/pisa_scan_selector.cc:163
+msgid " Update "
+msgstr " Bijwerken "
+
+#: frontend/pisa_view_manager.cc:762
+msgid " Continue "
+msgstr " Verder "
+
+#~ msgid "Adjust"
+#~ msgstr "Aanpassen"
diff --git a/po/pt.gmo b/po/pt.gmo
new file mode 100644
index 0000000..892d5ee
--- /dev/null
+++ b/po/pt.gmo
Binary files differ
diff --git a/po/pt.po b/po/pt.po
new file mode 100644
index 0000000..abeb9fb
--- /dev/null
+++ b/po/pt.po
@@ -0,0 +1,466 @@
+# pt.po -- Portuguese translations for `iscan' messages
+# Copyright (C) 2003, 2012 SEIKO EPSON CORPORATION
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: iscan 1.9.0\n"
+"Report-Msgid-Bugs-To: linux-printer@epson.jp\n"
+"POT-Creation-Date: 2019-08-21 08:06+0900\n"
+"PO-Revision-Date: 2011-05-30 15:57+0900\n"
+"Last-Translator: SEIKO EPSON CORPORATION\n"
+"Language-Team: Portuguese\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: \n"
+
+#: frontend/file-selector.cc:465
+msgid "Overwrite"
+msgstr "Substituir"
+
+#: frontend/file-selector.cc:465
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: frontend/file-selector.cc:746
+msgid "Save Options"
+msgstr "Salvar opções"
+
+#: frontend/file-selector.cc:765
+msgid "Create file with all pages"
+msgstr "Criar ficheiro todas pág."
+
+#: frontend/file-selector.cc:768
+msgid "Binding Position (for Double-Sided Scanning)"
+msgstr "Posição União(p/ Digit. 2 Faces)"
+
+#: frontend/file-selector.cc:769
+msgid "Left"
+msgstr "Esquerda"
+
+#: frontend/file-selector.cc:771
+msgid "Top"
+msgstr "Topo"
+
+#: frontend/file-selector.cc:776
+msgid "Determine File Type:"
+msgstr "Determinar tipo de arquivo:"
+
+#: frontend/file-selector.cc:835
+msgid "Start filing at:"
+msgstr "Começar a contar a partir de"
+
+#: frontend/file-selector.cc:858
+msgid "Number of digits:"
+msgstr "Número de dígitos"
+
+#: frontend/pisa_aleart_dialog.cc:73 frontend/pisa_configuration.cc:204
+#: frontend/pisa_scan_selector.cc:199
+msgid " OK "
+msgstr " OK "
+
+#: frontend/pisa_configuration.cc:113 frontend/pisa_main_window.cc:849
+msgid "Configuration"
+msgstr "Configuração"
+
+#: frontend/pisa_configuration.cc:163
+msgid "Print Command"
+msgstr "Comando de Impressão"
+
+#: frontend/pisa_configuration.cc:186
+msgid ""
+"In order to print, your print system must be able to handle the PNG file "
+"format directly. CUPS or Photo Image Print System (versions 1.3.1 or later) "
+"do this by default."
+msgstr ""
+
+#: frontend/pisa_configuration.cc:214 frontend/pisa_progress_window.cc:61
+#: frontend/pisa_scan_selector.cc:213 frontend/pisa_view_manager.cc:762
+msgid " Cancel "
+msgstr " Cancelar "
+
+#: frontend/pisa_error.cc:85
+msgid "Operation completed succesfully."
+msgstr ""
+
+#: frontend/pisa_error.cc:87
+msgid "Operation is not supported."
+msgstr ""
+
+#: frontend/pisa_error.cc:89
+msgid "Operation was cancelled."
+msgstr ""
+
+#: frontend/pisa_error.cc:91
+msgid "Device is busy---retry later."
+msgstr ""
+
+#: frontend/pisa_error.cc:93
+msgid "Data or argument is invalid."
+msgstr ""
+
+#: frontend/pisa_error.cc:95
+msgid "No more data available (end-of-file)."
+msgstr ""
+
+#: frontend/pisa_error.cc:97
+msgid ""
+"A paper jam occured. Open the Automatic Document Feeder and remove any "
+"paper."
+msgstr ""
+"Ocorreu um erro de papel.\n"
+"Abra o Alimentador Automático de Papel e remova todo o papel."
+
+#: frontend/pisa_error.cc:100
+msgid "Please load the document(s) into the Automatic Document Feeder."
+msgstr ""
+"Por favor coloque o(s) documento(s) no Alimentador Automático de Documentos."
+
+#: frontend/pisa_error.cc:103
+msgid ""
+"The automatic document feeder or scanner unit is open.\n"
+"Please close it."
+msgstr ""
+"A tampa do digitalizador ou do alimentador automático de documentos está "
+"aberta. Feche-a."
+
+#: frontend/pisa_error.cc:106
+msgid "Error during device I/O."
+msgstr ""
+
+#: frontend/pisa_error.cc:108
+msgid "Out of memory."
+msgstr ""
+
+#: frontend/pisa_error.cc:110
+msgid "Access to resource has been denied."
+msgstr ""
+
+#: frontend/pisa_error.cc:113
+msgid "There is not enough disk space for operation"
+msgstr "Não há espaço em disco suficiente para esta operação"
+
+#: frontend/pisa_error.cc:116
+msgid ""
+"Could not send command to scanner.\n"
+"Check the scanner's status."
+msgstr ""
+"Não foi possível enviar o comando para o digitalizador.\n"
+"Verifique o estado do digitalizador"
+
+#: frontend/pisa_error.cc:120
+msgid "Scanner model not supported"
+msgstr "Modelo de digitalizador não suportado"
+
+#: frontend/pisa_error.cc:123
+msgid ""
+"Selected area is too large for this resolution.\n"
+"Reduce the selected area or resolution."
+msgstr ""
+"A área seleccionada é demasiadamente grande para esta resolução.\n"
+"Reduza o valor da resolução ou da escala, ou reduza o tamanho da área "
+"seleccionada."
+
+#: frontend/pisa_error.cc:127 frontend/pisa_error.cc:130
+msgid "Could not create file"
+msgstr "Não foi possível criar ficheiro"
+
+#: frontend/pisa_error.cc:133
+msgid ""
+"A file with the same name already exists.\n"
+"Click \"Overwrite\" to replace the file or \"Cancel\" if you want to use "
+"another file name."
+msgstr ""
+"Já existe um ficheiro com o mesmo nome.\n"
+"Faça clique em \"Substituir\" para substituir o ficheiro ou em \"Cancelar\" "
+"se pretender utilizar outro nome de ficheiro."
+
+#: frontend/pisa_error.cc:138
+msgid ""
+"The Image Type setting you selected cannot be used with this resolution.\n"
+"Reduce the Resolution or Scale setting."
+msgstr ""
+"A definição de tipo de imagem que seleccionou não pode ser utilizada com "
+"esta resolução.Diminua a Resolução ou a Escala."
+
+#: frontend/pisa_error.cc:143
+msgid "Tray cover is closed. Please open the tray cover and then scan again."
+msgstr "A tampa da bandeja está fechada. Abra a tampa e tente novamente."
+
+#: frontend/pisa_error.cc:147
+msgid ""
+"A multi page feed occurred in the auto document feeder.\n"
+"Open the cover, remove the documents, and then try again. If documents "
+"remain on the tray, remove them and then reload them."
+msgstr ""
+"Foram alimentadas várias páginas no alimentador automático de documentos.\n"
+"Abra a tampa, retire os documentos e tente novamente. Se permanecerem "
+"documentos na bandeja, retire-os e volte a colocá-los."
+
+#: frontend/pisa_error.cc:156
+msgid "Unexpected error occurred"
+msgstr "Ocorreu um erro inesperado"
+
+#: frontend/pisa_gamma_correction.cc:482
+msgid " Reset "
+msgstr " Reiniciar "
+
+#: frontend/pisa_image_controls.cc:253
+msgid "Gamma:"
+msgstr "Gama:"
+
+#: frontend/pisa_image_controls.cc:268
+msgid "Highlight:"
+msgstr "Altas Luzes:"
+
+#: frontend/pisa_image_controls.cc:283
+msgid "Shadow:"
+msgstr "Sombra:"
+
+#: frontend/pisa_image_controls.cc:298
+msgid "Threshold:"
+msgstr "Limiar:"
+
+#: frontend/pisa_image_controls.cc:331
+msgid "Brightness:"
+msgstr "Brilho:"
+
+#: frontend/pisa_image_controls.cc:354
+msgid "Contrast:"
+msgstr "Contraste:"
+
+#: frontend/pisa_main_window.cc:90
+msgid "File"
+msgstr "Ficheiro"
+
+#: frontend/pisa_main_window.cc:91
+msgid "Printer"
+msgstr "Impressora"
+
+#: frontend/pisa_main_window.cc:104
+msgid "Flatbed"
+msgstr "Vidro"
+
+#: frontend/pisa_main_window.cc:105
+msgid "TPU - Negative film"
+msgstr "Leitor p/ neg."
+
+#: frontend/pisa_main_window.cc:106
+msgid "TPU - Positive film"
+msgstr "Leitor p/ pos."
+
+#: frontend/pisa_main_window.cc:107
+msgid "ADF - Single-sided"
+msgstr "AAD - 1 face"
+
+#: frontend/pisa_main_window.cc:108
+msgid "ADF - Double-sided"
+msgstr "AAD - 2 faces"
+
+#: frontend/pisa_main_window.cc:121
+msgid "Color Photo"
+msgstr "Fotografia a Cores"
+
+#: frontend/pisa_main_window.cc:122
+msgid "Color Document"
+msgstr "Documento Cor"
+
+#: frontend/pisa_main_window.cc:123
+msgid "Black & White Photo"
+msgstr "Fotografia a Preto & Branco"
+
+#: frontend/pisa_main_window.cc:124
+msgid "Black & White Document"
+msgstr "Documento P/B"
+
+#: frontend/pisa_main_window.cc:125
+msgid "Line Art"
+msgstr "Contornos"
+
+#: frontend/pisa_main_window.cc:169
+msgid "inches"
+msgstr "polegadas"
+
+#: frontend/pisa_main_window.cc:170
+msgid "pixels"
+msgstr "pixels"
+
+#: frontend/pisa_main_window.cc:171
+msgid "cm"
+msgstr "cm"
+
+#: frontend/pisa_main_window.cc:192
+msgid "Off"
+msgstr "Não"
+
+#: frontend/pisa_main_window.cc:193
+msgid "Standard"
+msgstr "Normal"
+
+#: frontend/pisa_main_window.cc:194
+msgid "Thin"
+msgstr "Fino"
+
+#: frontend/pisa_main_window.cc:299
+msgid "Calibration is failed."
+msgstr "A calibragem falhou."
+
+#: frontend/pisa_main_window.cc:325
+msgid "Cleaning is failed."
+msgstr "A limpeza falhou."
+
+#: frontend/pisa_main_window.cc:831
+msgid "Scan to File"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:837
+msgid "Scan"
+msgstr "Digitalizar"
+
+#: frontend/pisa_main_window.cc:903
+msgid "Close"
+msgstr "Fechar"
+
+#: frontend/pisa_main_window.cc:922
+msgid "Preview"
+msgstr "Antever"
+
+#: frontend/pisa_main_window.cc:923
+msgid "Zoom"
+msgstr "Zoom"
+
+#: frontend/pisa_main_window.cc:924
+msgid "Auto Exposure"
+msgstr "Exposição Auto"
+
+#: frontend/pisa_main_window.cc:927
+msgid "enable Start button"
+msgstr "Activar botão Iniciar"
+
+#: frontend/pisa_main_window.cc:928
+msgid "Speed priority scanning"
+msgstr "Digit.com prioridade veloc."
+
+#: frontend/pisa_main_window.cc:930
+msgid "Document Size - Auto Detect"
+msgstr "Tamanho do Documento - Detecção Automática"
+
+#: frontend/pisa_main_window.cc:932
+msgid "Correct Document Skew"
+msgstr "Corrigir Desvio Documento"
+
+#: frontend/pisa_main_window.cc:937
+msgid "Unsharp mask"
+msgstr "Filtro Suavizador"
+
+#: frontend/pisa_main_window.cc:941
+msgid "Detect Double Feed (Paper Thickness)"
+msgstr "Detectar Alimentação Dupla (Espessura do Papel)"
+
+#: frontend/pisa_main_window.cc:1027
+msgid "Scan to Print"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1073
+msgid "Destination:"
+msgstr "Destino:"
+
+#: frontend/pisa_main_window.cc:1152 frontend/pisa_scan_selector.cc:142
+msgid "Scanner:"
+msgstr "Digitalizador:"
+
+#: frontend/pisa_main_window.cc:1175
+msgid "Document"
+msgstr "Documento"
+
+#: frontend/pisa_main_window.cc:1186
+msgid "Image Controls"
+msgstr "Controlos de Imagem"
+
+#: frontend/pisa_main_window.cc:1195
+msgid "Tone Correction"
+msgstr "Correcção Tonal"
+
+#: frontend/pisa_main_window.cc:1233
+msgid "Document Source:"
+msgstr "Sist. Digitalização:"
+
+#: frontend/pisa_main_window.cc:1266
+msgid "Image Type:"
+msgstr "Tipo de Imagem:"
+
+#: frontend/pisa_main_window.cc:1278
+msgid "Resolution:"
+msgstr "Resolução:"
+
+#: frontend/pisa_main_window.cc:1327
+msgid "Target"
+msgstr "Alvo"
+
+#: frontend/pisa_main_window.cc:1338
+msgid "W:"
+msgstr "L:"
+
+#: frontend/pisa_main_window.cc:1350
+msgid "H:"
+msgstr "A:"
+
+#: frontend/pisa_main_window.cc:1375
+msgid "Scale"
+msgstr "Escala"
+
+#: frontend/pisa_main_window.cc:1399
+msgid "Focus"
+msgstr "Focagem"
+
+#: frontend/pisa_main_window.cc:1429
+msgid "Options"
+msgstr "Opções"
+
+#: frontend/pisa_main_window.cc:1720
+msgid "Calibration"
+msgstr "Calibragem"
+
+#: frontend/pisa_main_window.cc:1731
+msgid "Cleaning"
+msgstr "Limpeza"
+
+#: frontend/pisa_progress_window.cc:62 frontend/pisa_view_manager.cc:774
+msgid " Finish "
+msgstr " Terminar "
+
+#: frontend/pisa_progress_window.cc:171
+msgid "Scanner is warming up. Please wait..."
+msgstr "O digitalizador está a aquecer. É favor aguardar..."
+
+#: frontend/pisa_progress_window.cc:174
+msgid "Pre-scanning in Progress"
+msgstr "Pré-digitalização em curso"
+
+#: frontend/pisa_progress_window.cc:177
+msgid "Scanning in Progress"
+msgstr "Digitalização em curso"
+
+#: frontend/pisa_progress_window.cc:180
+msgid ""
+"Starting a sequence of scans.\n"
+"Press to scanner's Start button to start each scan."
+msgstr ""
+"Iniciar uma sequência de digitalizações.\n"
+"Pressione o botão Iniciar do digitalizador para iniciar cada uma das "
+"digitalizações."
+
+#: frontend/pisa_scan_selector.cc:103
+msgid "Scan Selector Dialog"
+msgstr ""
+
+#: frontend/pisa_scan_selector.cc:163
+msgid " Update "
+msgstr " Actualizar "
+
+#: frontend/pisa_view_manager.cc:762
+msgid " Continue "
+msgstr " Continuar "
+
+#~ msgid "Adjust"
+#~ msgstr "Ajuste"
diff --git a/po/quot.sed b/po/quot.sed
new file mode 100644
index 0000000..0122c46
--- /dev/null
+++ b/po/quot.sed
@@ -0,0 +1,6 @@
+s/"\([^"]*\)"/“\1”/g
+s/`\([^`']*\)'/‘\1’/g
+s/ '\([^`']*\)' / ‘\1’ /g
+s/ '\([^`']*\)'$/ ‘\1’/g
+s/^'\([^`']*\)' /‘\1’ /g
+s/“”/""/g
diff --git a/po/remove-potcdate.sin b/po/remove-potcdate.sin
new file mode 100644
index 0000000..2436c49
--- /dev/null
+++ b/po/remove-potcdate.sin
@@ -0,0 +1,19 @@
+# Sed script that remove the POT-Creation-Date line in the header entry
+# from a POT file.
+#
+# The distinction between the first and the following occurrences of the
+# pattern is achieved by looking at the hold space.
+/^"POT-Creation-Date: .*"$/{
+x
+# Test if the hold space is empty.
+s/P/P/
+ta
+# Yes it was empty. First occurrence. Remove the line.
+g
+d
+bb
+:a
+# The hold space was nonempty. Following occurrences. Do nothing.
+x
+:b
+}
diff --git a/po/stamp-po b/po/stamp-po
new file mode 100644
index 0000000..9788f70
--- /dev/null
+++ b/po/stamp-po
@@ -0,0 +1 @@
+timestamp
diff --git a/po/zh_CN.gmo b/po/zh_CN.gmo
new file mode 100644
index 0000000..50bf103
--- /dev/null
+++ b/po/zh_CN.gmo
Binary files differ
diff --git a/po/zh_CN.po b/po/zh_CN.po
new file mode 100644
index 0000000..979bccc
--- /dev/null
+++ b/po/zh_CN.po
@@ -0,0 +1,455 @@
+# zh_CN.po - simplified Chinese message strings for Image Scan!
+# Copyright (C) 2003, 2012 SEIKO EPSON CORPORATION
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: iscan 1.9.0\n"
+"Report-Msgid-Bugs-To: linux-printer@epson.jp\n"
+"POT-Creation-Date: 2019-08-21 08:06+0900\n"
+"PO-Revision-Date: 2011-05-30 16:12+0900\n"
+"Last-Translator: SEIKO EPSON CORPORATION\n"
+"Language-Team: Chinese (Simplified)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: \n"
+
+#: frontend/file-selector.cc:465
+msgid "Overwrite"
+msgstr "覆盖"
+
+#: frontend/file-selector.cc:465
+msgid "Cancel"
+msgstr "取消"
+
+#: frontend/file-selector.cc:746
+msgid "Save Options"
+msgstr "保存设置"
+
+#: frontend/file-selector.cc:765
+msgid "Create file with all pages"
+msgstr ""
+
+#: frontend/file-selector.cc:768
+msgid "Binding Position (for Double-Sided Scanning)"
+msgstr "装订位置(用于双面扫描)"
+
+#: frontend/file-selector.cc:769
+msgid "Left"
+msgstr "左"
+
+#: frontend/file-selector.cc:771
+msgid "Top"
+msgstr "上"
+
+#: frontend/file-selector.cc:776
+msgid "Determine File Type:"
+msgstr "确定文件类型"
+
+#: frontend/file-selector.cc:835
+msgid "Start filing at:"
+msgstr "起始号"
+
+#: frontend/file-selector.cc:858
+msgid "Number of digits:"
+msgstr "号码位数"
+
+#: frontend/pisa_aleart_dialog.cc:73 frontend/pisa_configuration.cc:204
+#: frontend/pisa_scan_selector.cc:199
+msgid " OK "
+msgstr " 确定 "
+
+#: frontend/pisa_configuration.cc:113 frontend/pisa_main_window.cc:849
+msgid "Configuration"
+msgstr "配置"
+
+#: frontend/pisa_configuration.cc:163
+msgid "Print Command"
+msgstr "打印命令"
+
+#: frontend/pisa_configuration.cc:186
+msgid ""
+"In order to print, your print system must be able to handle the PNG file "
+"format directly. CUPS or Photo Image Print System (versions 1.3.1 or later) "
+"do this by default."
+msgstr ""
+
+#: frontend/pisa_configuration.cc:214 frontend/pisa_progress_window.cc:61
+#: frontend/pisa_scan_selector.cc:213 frontend/pisa_view_manager.cc:762
+msgid " Cancel "
+msgstr " 取消 "
+
+#: frontend/pisa_error.cc:85
+msgid "Operation completed succesfully."
+msgstr ""
+
+#: frontend/pisa_error.cc:87
+msgid "Operation is not supported."
+msgstr ""
+
+#: frontend/pisa_error.cc:89
+msgid "Operation was cancelled."
+msgstr ""
+
+#: frontend/pisa_error.cc:91
+msgid "Device is busy---retry later."
+msgstr ""
+
+#: frontend/pisa_error.cc:93
+msgid "Data or argument is invalid."
+msgstr ""
+
+#: frontend/pisa_error.cc:95
+msgid "No more data available (end-of-file)."
+msgstr ""
+
+#: frontend/pisa_error.cc:97
+msgid ""
+"A paper jam occured. Open the Automatic Document Feeder and remove any "
+"paper."
+msgstr "卡纸。打开自动文稿送纸器并且移除所卡纸张。"
+
+#: frontend/pisa_error.cc:100
+msgid "Please load the document(s) into the Automatic Document Feeder."
+msgstr "请在自动文稿送纸器中放入文稿。"
+
+#: frontend/pisa_error.cc:103
+msgid ""
+"The automatic document feeder or scanner unit is open.\n"
+"Please close it."
+msgstr ""
+"自动文稿送纸器或扫描仪被打开。\n"
+"请关闭它。"
+
+#: frontend/pisa_error.cc:106
+msgid "Error during device I/O."
+msgstr ""
+
+#: frontend/pisa_error.cc:108
+msgid "Out of memory."
+msgstr ""
+
+#: frontend/pisa_error.cc:110
+msgid "Access to resource has been denied."
+msgstr ""
+
+#: frontend/pisa_error.cc:113
+msgid "There is not enough disk space for operation"
+msgstr "没有充足的磁盘空间运行"
+
+#: frontend/pisa_error.cc:116
+msgid ""
+"Could not send command to scanner.\n"
+"Check the scanner's status."
+msgstr ""
+"不能发送指令到扫描仪。\n"
+"请检查扫描仪状态。"
+
+#: frontend/pisa_error.cc:120
+msgid "Scanner model not supported"
+msgstr "不支持这种扫描仪模型"
+
+#: frontend/pisa_error.cc:123
+msgid ""
+"Selected area is too large for this resolution.\n"
+"Reduce the selected area or resolution."
+msgstr ""
+"对于该分辨率被选区域过大。\n"
+"请缩小被选区域或降低分辨率。"
+
+#: frontend/pisa_error.cc:127 frontend/pisa_error.cc:130
+msgid "Could not create file"
+msgstr "不能创建文件"
+
+#: frontend/pisa_error.cc:133
+msgid ""
+"A file with the same name already exists.\n"
+"Click \"Overwrite\" to replace the file or \"Cancel\" if you want to use "
+"another file name."
+msgstr ""
+"具有同名的文件已存在。\n"
+"单击“覆盖”替换文件,或单击“取消”使用其他文件名。"
+
+#: frontend/pisa_error.cc:138
+msgid ""
+"The Image Type setting you selected cannot be used with this resolution.\n"
+"Reduce the Resolution or Scale setting."
+msgstr "您选择的图像类型设置不能使用此分辨率,请降低分辨率或比例设置。"
+
+#: frontend/pisa_error.cc:143
+msgid "Tray cover is closed. Please open the tray cover and then scan again."
+msgstr ""
+
+#: frontend/pisa_error.cc:147
+msgid ""
+"A multi page feed occurred in the auto document feeder.\n"
+"Open the cover, remove the documents, and then try again. If documents "
+"remain on the tray, remove them and then reload them."
+msgstr ""
+
+#: frontend/pisa_error.cc:156
+msgid "Unexpected error occurred"
+msgstr "出现意外错误"
+
+#: frontend/pisa_gamma_correction.cc:482
+msgid " Reset "
+msgstr " 重置 "
+
+#: frontend/pisa_image_controls.cc:253
+msgid "Gamma:"
+msgstr "Gamma:"
+
+#: frontend/pisa_image_controls.cc:268
+msgid "Highlight:"
+msgstr "高光:"
+
+#: frontend/pisa_image_controls.cc:283
+msgid "Shadow:"
+msgstr "阴影:"
+
+#: frontend/pisa_image_controls.cc:298
+msgid "Threshold:"
+msgstr "阈值:"
+
+#: frontend/pisa_image_controls.cc:331
+msgid "Brightness:"
+msgstr "亮度:"
+
+#: frontend/pisa_image_controls.cc:354
+msgid "Contrast:"
+msgstr "对比度:"
+
+#: frontend/pisa_main_window.cc:90
+msgid "File"
+msgstr "文件"
+
+#: frontend/pisa_main_window.cc:91
+msgid "Printer"
+msgstr "打印机"
+
+#: frontend/pisa_main_window.cc:104
+msgid "Flatbed"
+msgstr "平板"
+
+#: frontend/pisa_main_window.cc:105
+msgid "TPU - Negative film"
+msgstr "透明负胶片"
+
+#: frontend/pisa_main_window.cc:106
+msgid "TPU - Positive film"
+msgstr "透明正胶片"
+
+#: frontend/pisa_main_window.cc:107
+msgid "ADF - Single-sided"
+msgstr "ADF - 单面"
+
+#: frontend/pisa_main_window.cc:108
+msgid "ADF - Double-sided"
+msgstr "ADF - 双面"
+
+#: frontend/pisa_main_window.cc:121
+msgid "Color Photo"
+msgstr "彩色照片"
+
+#: frontend/pisa_main_window.cc:122
+msgid "Color Document"
+msgstr "彩色文档"
+
+#: frontend/pisa_main_window.cc:123
+msgid "Black & White Photo"
+msgstr "黑白照片"
+
+#: frontend/pisa_main_window.cc:124
+msgid "Black & White Document"
+msgstr "黑白文档"
+
+#: frontend/pisa_main_window.cc:125
+msgid "Line Art"
+msgstr "线画"
+
+#: frontend/pisa_main_window.cc:169
+msgid "inches"
+msgstr "英寸"
+
+#: frontend/pisa_main_window.cc:170
+msgid "pixels"
+msgstr "像素"
+
+#: frontend/pisa_main_window.cc:171
+msgid "cm"
+msgstr "厘米"
+
+#: frontend/pisa_main_window.cc:192
+msgid "Off"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:193
+msgid "Standard"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:194
+msgid "Thin"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:299
+msgid "Calibration is failed."
+msgstr "校准失败。"
+
+#: frontend/pisa_main_window.cc:325
+msgid "Cleaning is failed."
+msgstr "清洗失败。"
+
+#: frontend/pisa_main_window.cc:831
+msgid "Scan to File"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:837
+msgid "Scan"
+msgstr "扫描"
+
+#: frontend/pisa_main_window.cc:903
+msgid "Close"
+msgstr "关闭"
+
+#: frontend/pisa_main_window.cc:922
+msgid "Preview"
+msgstr "预览"
+
+#: frontend/pisa_main_window.cc:923
+msgid "Zoom"
+msgstr "缩放"
+
+#: frontend/pisa_main_window.cc:924
+msgid "Auto Exposure"
+msgstr "自动曝光"
+
+#: frontend/pisa_main_window.cc:927
+msgid "enable Start button"
+msgstr "可使用[开始按钮]"
+
+#: frontend/pisa_main_window.cc:928
+msgid "Speed priority scanning"
+msgstr "速度优先扫描"
+
+#: frontend/pisa_main_window.cc:930
+msgid "Document Size - Auto Detect"
+msgstr "文档大小 - 自动检测"
+
+#: frontend/pisa_main_window.cc:932
+msgid "Correct Document Skew"
+msgstr "校正文稿歪斜"
+
+#: frontend/pisa_main_window.cc:937
+msgid "Unsharp mask"
+msgstr "锐化图像"
+
+#: frontend/pisa_main_window.cc:941
+msgid "Detect Double Feed (Paper Thickness)"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1027
+msgid "Scan to Print"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1073
+msgid "Destination:"
+msgstr "目标设备:"
+
+#: frontend/pisa_main_window.cc:1152 frontend/pisa_scan_selector.cc:142
+msgid "Scanner:"
+msgstr "扫描仪"
+
+#: frontend/pisa_main_window.cc:1175
+msgid "Document"
+msgstr "文稿"
+
+#: frontend/pisa_main_window.cc:1186
+msgid "Image Controls"
+msgstr "图像控制"
+
+#: frontend/pisa_main_window.cc:1195
+msgid "Tone Correction"
+msgstr "色调校正"
+
+#: frontend/pisa_main_window.cc:1233
+msgid "Document Source:"
+msgstr "文稿来源:"
+
+#: frontend/pisa_main_window.cc:1266
+msgid "Image Type:"
+msgstr "图像类型:"
+
+#: frontend/pisa_main_window.cc:1278
+msgid "Resolution:"
+msgstr "分辨率:"
+
+#: frontend/pisa_main_window.cc:1327
+msgid "Target"
+msgstr "扫描图像"
+
+#: frontend/pisa_main_window.cc:1338
+msgid "W:"
+msgstr "宽:"
+
+#: frontend/pisa_main_window.cc:1350
+msgid "H:"
+msgstr "高:"
+
+#: frontend/pisa_main_window.cc:1375
+msgid "Scale"
+msgstr "比例"
+
+#: frontend/pisa_main_window.cc:1399
+msgid "Focus"
+msgstr "聚焦"
+
+#: frontend/pisa_main_window.cc:1429
+msgid "Options"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1720
+msgid "Calibration"
+msgstr "校准"
+
+#: frontend/pisa_main_window.cc:1731
+msgid "Cleaning"
+msgstr "清洗"
+
+#: frontend/pisa_progress_window.cc:62 frontend/pisa_view_manager.cc:774
+msgid " Finish "
+msgstr " 完成 "
+
+#: frontend/pisa_progress_window.cc:171
+msgid "Scanner is warming up. Please wait..."
+msgstr "扫描仪正在预热。请稍候…"
+
+#: frontend/pisa_progress_window.cc:174
+msgid "Pre-scanning in Progress"
+msgstr "正在预览…"
+
+#: frontend/pisa_progress_window.cc:177
+msgid "Scanning in Progress"
+msgstr "正在扫描…"
+
+#: frontend/pisa_progress_window.cc:180
+msgid ""
+"Starting a sequence of scans.\n"
+"Press to scanner's Start button to start each scan."
+msgstr ""
+"开始一个扫描序列。\n"
+"按下扫描仪上的[开始按钮]开始每个扫描工作。"
+
+#: frontend/pisa_scan_selector.cc:103
+msgid "Scan Selector Dialog"
+msgstr ""
+
+#: frontend/pisa_scan_selector.cc:163
+msgid " Update "
+msgstr " 更新 "
+
+#: frontend/pisa_view_manager.cc:762
+msgid " Continue "
+msgstr " 继续 "
+
+#~ msgid "Adjust"
+#~ msgstr "调整"
diff --git a/po/zh_TW.gmo b/po/zh_TW.gmo
new file mode 100644
index 0000000..cfbd6eb
--- /dev/null
+++ b/po/zh_TW.gmo
Binary files differ
diff --git a/po/zh_TW.po b/po/zh_TW.po
new file mode 100644
index 0000000..a5e84e3
--- /dev/null
+++ b/po/zh_TW.po
@@ -0,0 +1,456 @@
+# zh_TW.po -- traditional Chinese message strings for Image Scan!
+# Copyright (C) 2003, 2012 SEIKO EPSON CORPORATION
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: iscan 1.9.0\n"
+"Report-Msgid-Bugs-To: linux-printer@epson.jp\n"
+"POT-Creation-Date: 2019-08-21 08:06+0900\n"
+"PO-Revision-Date: 2011-05-30 16:12+0900\n"
+"Last-Translator: SEIKO EPSON CORPORATION\n"
+"Language-Team: Chinese (Traditional)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: \n"
+
+#: frontend/file-selector.cc:465
+msgid "Overwrite"
+msgstr "覆寫"
+
+#: frontend/file-selector.cc:465
+msgid "Cancel"
+msgstr "取消"
+
+#: frontend/file-selector.cc:746
+msgid "Save Options"
+msgstr "儲存選項"
+
+#: frontend/file-selector.cc:765
+msgid "Create file with all pages"
+msgstr ""
+
+#: frontend/file-selector.cc:768
+msgid "Binding Position (for Double-Sided Scanning)"
+msgstr "裝訂位置(雙面掃描)"
+
+#: frontend/file-selector.cc:769
+msgid "Left"
+msgstr "左"
+
+#: frontend/file-selector.cc:771
+msgid "Top"
+msgstr "上"
+
+#: frontend/file-selector.cc:776
+msgid "Determine File Type:"
+msgstr "指定檔案類型"
+
+#: frontend/file-selector.cc:835
+msgid "Start filing at:"
+msgstr "起始位數"
+
+#: frontend/file-selector.cc:858
+msgid "Number of digits:"
+msgstr "數字位數"
+
+#: frontend/pisa_aleart_dialog.cc:73 frontend/pisa_configuration.cc:204
+#: frontend/pisa_scan_selector.cc:199
+msgid " OK "
+msgstr " 確定 "
+
+#: frontend/pisa_configuration.cc:113 frontend/pisa_main_window.cc:849
+msgid "Configuration"
+msgstr "組態"
+
+#: frontend/pisa_configuration.cc:163
+msgid "Print Command"
+msgstr "列印指令"
+
+#: frontend/pisa_configuration.cc:186
+msgid ""
+"In order to print, your print system must be able to handle the PNG file "
+"format directly. CUPS or Photo Image Print System (versions 1.3.1 or later) "
+"do this by default."
+msgstr ""
+
+#: frontend/pisa_configuration.cc:214 frontend/pisa_progress_window.cc:61
+#: frontend/pisa_scan_selector.cc:213 frontend/pisa_view_manager.cc:762
+msgid " Cancel "
+msgstr " 取消 "
+
+#: frontend/pisa_error.cc:85
+msgid "Operation completed succesfully."
+msgstr ""
+
+#: frontend/pisa_error.cc:87
+msgid "Operation is not supported."
+msgstr ""
+
+#: frontend/pisa_error.cc:89
+msgid "Operation was cancelled."
+msgstr ""
+
+#: frontend/pisa_error.cc:91
+msgid "Device is busy---retry later."
+msgstr ""
+
+#: frontend/pisa_error.cc:93
+msgid "Data or argument is invalid."
+msgstr ""
+
+#: frontend/pisa_error.cc:95
+msgid "No more data available (end-of-file)."
+msgstr ""
+
+#: frontend/pisa_error.cc:97
+msgid ""
+"A paper jam occured. Open the Automatic Document Feeder and remove any "
+"paper."
+msgstr "發生夾紙。請打開自動送紙器並取出夾紙。"
+
+#: frontend/pisa_error.cc:100
+msgid "Please load the document(s) into the Automatic Document Feeder."
+msgstr "請將紙張裝入自動送紙器。"
+
+#: frontend/pisa_error.cc:103
+msgid ""
+"The automatic document feeder or scanner unit is open.\n"
+"Please close it."
+msgstr ""
+"自動送紙器或掃描器單元開啟。\n"
+"請將它闔上。"
+
+#: frontend/pisa_error.cc:106
+msgid "Error during device I/O."
+msgstr ""
+
+#: frontend/pisa_error.cc:108
+msgid "Out of memory."
+msgstr ""
+
+#: frontend/pisa_error.cc:110
+msgid "Access to resource has been denied."
+msgstr ""
+
+#: frontend/pisa_error.cc:113
+msgid "There is not enough disk space for operation"
+msgstr "沒有足夠的磁碟空間可執行"
+
+#: frontend/pisa_error.cc:116
+msgid ""
+"Could not send command to scanner.\n"
+"Check the scanner's status."
+msgstr ""
+"無法傳送指令到掃描器。\n"
+"請檢查掃描器的狀態。"
+
+#: frontend/pisa_error.cc:120
+msgid "Scanner model not supported"
+msgstr "掃描模式沒有支援"
+
+#: frontend/pisa_error.cc:123
+msgid ""
+"Selected area is too large for this resolution.\n"
+"Reduce the selected area or resolution."
+msgstr ""
+"您所選取的區域對於這個解析度而言太大了。\n"
+"請縮小選取的區域或解析度。"
+
+#: frontend/pisa_error.cc:127 frontend/pisa_error.cc:130
+msgid "Could not create file"
+msgstr "無法新增檔案"
+
+#: frontend/pisa_error.cc:133
+msgid ""
+"A file with the same name already exists.\n"
+"Click \"Overwrite\" to replace the file or \"Cancel\" if you want to use "
+"another file name."
+msgstr ""
+"已經有相同名稱的檔案存在。\n"
+"按一下〝覆寫〞取代檔案,或按一下〝取消〞使用其他檔案名稱。"
+
+#: frontend/pisa_error.cc:138
+msgid ""
+"The Image Type setting you selected cannot be used with this resolution.\n"
+"Reduce the Resolution or Scale setting."
+msgstr ""
+"您所選擇的這個影像類型設定無法在這個解析度下被使用。請降低解析度或縮放設定。"
+
+#: frontend/pisa_error.cc:143
+msgid "Tray cover is closed. Please open the tray cover and then scan again."
+msgstr ""
+
+#: frontend/pisa_error.cc:147
+msgid ""
+"A multi page feed occurred in the auto document feeder.\n"
+"Open the cover, remove the documents, and then try again. If documents "
+"remain on the tray, remove them and then reload them."
+msgstr ""
+
+#: frontend/pisa_error.cc:156
+msgid "Unexpected error occurred"
+msgstr "發生不可預期的錯誤"
+
+#: frontend/pisa_gamma_correction.cc:482
+msgid " Reset "
+msgstr " 重置 "
+
+#: frontend/pisa_image_controls.cc:253
+msgid "Gamma:"
+msgstr "Gamma:"
+
+#: frontend/pisa_image_controls.cc:268
+msgid "Highlight:"
+msgstr "亮部:"
+
+#: frontend/pisa_image_controls.cc:283
+msgid "Shadow:"
+msgstr "暗部:"
+
+#: frontend/pisa_image_controls.cc:298
+msgid "Threshold:"
+msgstr "高反差:"
+
+#: frontend/pisa_image_controls.cc:331
+msgid "Brightness:"
+msgstr "亮度︰"
+
+#: frontend/pisa_image_controls.cc:354
+msgid "Contrast:"
+msgstr "對比︰"
+
+#: frontend/pisa_main_window.cc:90
+msgid "File"
+msgstr "檔案"
+
+#: frontend/pisa_main_window.cc:91
+msgid "Printer"
+msgstr "印表機"
+
+#: frontend/pisa_main_window.cc:104
+msgid "Flatbed"
+msgstr "平台式"
+
+#: frontend/pisa_main_window.cc:105
+msgid "TPU - Negative film"
+msgstr "光罩:負片"
+
+#: frontend/pisa_main_window.cc:106
+msgid "TPU - Positive film"
+msgstr "光罩:正片"
+
+#: frontend/pisa_main_window.cc:107
+msgid "ADF - Single-sided"
+msgstr "ADF - 單面"
+
+#: frontend/pisa_main_window.cc:108
+msgid "ADF - Double-sided"
+msgstr "ADF - 雙面"
+
+#: frontend/pisa_main_window.cc:121
+msgid "Color Photo"
+msgstr "彩色相片"
+
+#: frontend/pisa_main_window.cc:122
+msgid "Color Document"
+msgstr "彩色文件"
+
+#: frontend/pisa_main_window.cc:123
+msgid "Black & White Photo"
+msgstr "黑白相片"
+
+#: frontend/pisa_main_window.cc:124
+msgid "Black & White Document"
+msgstr "黑白文件"
+
+#: frontend/pisa_main_window.cc:125
+msgid "Line Art"
+msgstr "線條"
+
+#: frontend/pisa_main_window.cc:169
+msgid "inches"
+msgstr "英吋"
+
+#: frontend/pisa_main_window.cc:170
+msgid "pixels"
+msgstr "畫素"
+
+#: frontend/pisa_main_window.cc:171
+msgid "cm"
+msgstr "公分"
+
+#: frontend/pisa_main_window.cc:192
+msgid "Off"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:193
+msgid "Standard"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:194
+msgid "Thin"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:299
+msgid "Calibration is failed."
+msgstr "校正失敗。"
+
+#: frontend/pisa_main_window.cc:325
+msgid "Cleaning is failed."
+msgstr "清潔失敗。"
+
+#: frontend/pisa_main_window.cc:831
+msgid "Scan to File"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:837
+msgid "Scan"
+msgstr "掃瞄"
+
+#: frontend/pisa_main_window.cc:903
+msgid "Close"
+msgstr "關閉"
+
+#: frontend/pisa_main_window.cc:922
+msgid "Preview"
+msgstr "預視"
+
+#: frontend/pisa_main_window.cc:923
+msgid "Zoom"
+msgstr "局部放大"
+
+#: frontend/pisa_main_window.cc:924
+msgid "Auto Exposure"
+msgstr "自動曝光"
+
+#: frontend/pisa_main_window.cc:927
+msgid "enable Start button"
+msgstr "啟用『開啟』鍵"
+
+#: frontend/pisa_main_window.cc:928
+msgid "Speed priority scanning"
+msgstr "速度優先掃描"
+
+#: frontend/pisa_main_window.cc:930
+msgid "Document Size - Auto Detect"
+msgstr "文件大小 - 自動偵測"
+
+#: frontend/pisa_main_window.cc:932
+msgid "Correct Document Skew"
+msgstr "文件歪斜校正"
+
+#: frontend/pisa_main_window.cc:937
+msgid "Unsharp mask"
+msgstr "影像邊緣銳利化"
+
+#: frontend/pisa_main_window.cc:941
+msgid "Detect Double Feed (Paper Thickness)"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1027
+msgid "Scan to Print"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1073
+msgid "Destination:"
+msgstr "目的地:"
+
+#: frontend/pisa_main_window.cc:1152 frontend/pisa_scan_selector.cc:142
+msgid "Scanner:"
+msgstr "掃描器"
+
+#: frontend/pisa_main_window.cc:1175
+msgid "Document"
+msgstr "文件"
+
+#: frontend/pisa_main_window.cc:1186
+msgid "Image Controls"
+msgstr "影像控制"
+
+#: frontend/pisa_main_window.cc:1195
+msgid "Tone Correction"
+msgstr "色調校正"
+
+#: frontend/pisa_main_window.cc:1233
+msgid "Document Source:"
+msgstr "文件來源:"
+
+#: frontend/pisa_main_window.cc:1266
+msgid "Image Type:"
+msgstr "影像形式:"
+
+#: frontend/pisa_main_window.cc:1278
+msgid "Resolution:"
+msgstr "解析度:"
+
+#: frontend/pisa_main_window.cc:1327
+msgid "Target"
+msgstr "輸出"
+
+#: frontend/pisa_main_window.cc:1338
+msgid "W:"
+msgstr "W:"
+
+#: frontend/pisa_main_window.cc:1350
+msgid "H:"
+msgstr "H:"
+
+#: frontend/pisa_main_window.cc:1375
+msgid "Scale"
+msgstr "縮放"
+
+#: frontend/pisa_main_window.cc:1399
+msgid "Focus"
+msgstr "對焦"
+
+#: frontend/pisa_main_window.cc:1429
+msgid "Options"
+msgstr ""
+
+#: frontend/pisa_main_window.cc:1720
+msgid "Calibration"
+msgstr "校正"
+
+#: frontend/pisa_main_window.cc:1731
+msgid "Cleaning"
+msgstr "清潔"
+
+#: frontend/pisa_progress_window.cc:62 frontend/pisa_view_manager.cc:774
+msgid " Finish "
+msgstr " 結束 "
+
+#: frontend/pisa_progress_window.cc:171
+msgid "Scanner is warming up. Please wait..."
+msgstr "掃描器暖機中。請稍候..."
+
+#: frontend/pisa_progress_window.cc:174
+msgid "Pre-scanning in Progress"
+msgstr "預掃描進行中"
+
+#: frontend/pisa_progress_window.cc:177
+msgid "Scanning in Progress"
+msgstr "掃描進行"
+
+#: frontend/pisa_progress_window.cc:180
+msgid ""
+"Starting a sequence of scans.\n"
+"Press to scanner's Start button to start each scan."
+msgstr ""
+"開啟掃描程序。\n"
+"請按下『開啟』鍵以開始掃描。"
+
+#: frontend/pisa_scan_selector.cc:103
+msgid "Scan Selector Dialog"
+msgstr ""
+
+#: frontend/pisa_scan_selector.cc:163
+msgid " Update "
+msgstr " 更新 "
+
+#: frontend/pisa_view_manager.cc:762
+msgid " Continue "
+msgstr " 繼續 "
+
+#~ msgid "Adjust"
+#~ msgstr "校準"
diff --git a/sanei/linux_sg3_err.h b/sanei/linux_sg3_err.h
new file mode 100644
index 0000000..53198c0
--- /dev/null
+++ b/sanei/linux_sg3_err.h
@@ -0,0 +1,135 @@
+/* sane - Scanner Access Now Easy.
+
+ This file is part of the SANE package.
+
+ SANE is free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ SANE is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with sane; see the file COPYING. If not, write to the Free
+ Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+*/
+
+#ifndef SG_ERR_H
+#define SG_ERR_H
+
+/* Linux sg error codes taken from Doug Gilbert's sg_utils:
+ http://www.torque.net/sg/ */
+
+/* Some of the following error/status codes are exchanged between the
+ various layers of the SCSI sub-system in Linux and should never
+ reach the user. They are placed here for completeness. What appears
+ here is copied from drivers/scsi/scsi.h which is not visible in
+ the user space. */
+
+/* The following are 'host_status' codes */
+#ifndef DID_OK
+#define DID_OK 0x00
+#endif
+#ifndef DID_NO_CONNECT
+#define DID_NO_CONNECT 0x01 /* Unable to connect before timeout */
+#define DID_BUS_BUSY 0x02 /* Bus remain busy until timeout */
+#define DID_TIME_OUT 0x03 /* Timed out for some other reason */
+#define DID_BAD_TARGET 0x04 /* Bad target (id?) */
+#define DID_ABORT 0x05 /* Told to abort for some other reason */
+#define DID_PARITY 0x06 /* Parity error (on SCSI bus) */
+#define DID_ERROR 0x07 /* Internal error */
+#define DID_RESET 0x08 /* Reset by somebody */
+#define DID_BAD_INTR 0x09 /* Received an unexpected interrupt */
+#define DID_PASSTHROUGH 0x0a /* Force command past mid-level */
+#define DID_SOFT_ERROR 0x0b /* The low-level driver wants a retry */
+#endif
+
+
+
+/* These defines are to isolate applictaions from kernel define changes */
+#define SG_ERR_DID_OK DID_OK
+#define SG_ERR_DID_NO_CONNECT DID_NO_CONNECT
+#define SG_ERR_DID_BUS_BUSY DID_BUS_BUSY
+#define SG_ERR_DID_TIME_OUT DID_TIME_OUT
+#define SG_ERR_DID_BAD_TARGET DID_BAD_TARGET
+#define SG_ERR_DID_ABORT DID_ABORT
+#define SG_ERR_DID_PARITY DID_PARITY
+#define SG_ERR_DID_ERROR DID_ERROR
+#define SG_ERR_DID_RESET DID_RESET
+#define SG_ERR_DID_BAD_INTR DID_BAD_INTR
+#define SG_ERR_DID_PASSTHROUGH DID_PASSTHROUGH
+#define SG_ERR_DID_SOFT_ERROR DID_SOFT_ERROR
+
+/* The following are 'driver_status' codes */
+#ifndef DRIVER_OK
+#define DRIVER_OK 0x00
+#endif
+#ifndef DRIVER_BUSY
+#define DRIVER_BUSY 0x01
+#define DRIVER_SOFT 0x02
+#define DRIVER_MEDIA 0x03
+#define DRIVER_ERROR 0x04
+#define DRIVER_INVALID 0x05
+#define DRIVER_TIMEOUT 0x06
+#define DRIVER_HARD 0x07
+#define DRIVER_SENSE 0x08 /* Sense_buffer has been set */
+
+/* Following "suggests" are "or-ed" with one of previous 8 entries */
+#define SUGGEST_RETRY 0x10
+#define SUGGEST_ABORT 0x20
+#define SUGGEST_REMAP 0x30
+#define SUGGEST_DIE 0x40
+#define SUGGEST_SENSE 0x80
+#define SUGGEST_IS_OK 0xff
+#endif
+#ifndef DRIVER_MASK
+#define DRIVER_MASK 0x0f
+#endif
+#ifndef SUGGEST_MASK
+#define SUGGEST_MASK 0xf0
+#endif
+
+/* These defines are to isolate applictaions from kernel define changes */
+#define SG_ERR_DRIVER_OK DRIVER_OK
+#define SG_ERR_DRIVER_BUSY DRIVER_BUSY
+#define SG_ERR_DRIVER_SOFT DRIVER_SOFT
+#define SG_ERR_DRIVER_MEDIA DRIVER_MEDIA
+#define SG_ERR_DRIVER_ERROR DRIVER_ERROR
+#define SG_ERR_DRIVER_INVALID DRIVER_INVALID
+#define SG_ERR_DRIVER_TIMEOUT DRIVER_TIMEOUT
+#define SG_ERR_DRIVER_HARD DRIVER_HARD
+#define SG_ERR_DRIVER_SENSE DRIVER_SENSE
+#define SG_ERR_SUGGEST_RETRY SUGGEST_RETRY
+#define SG_ERR_SUGGEST_ABORT SUGGEST_ABORT
+#define SG_ERR_SUGGEST_REMAP SUGGEST_REMAP
+#define SG_ERR_SUGGEST_DIE SUGGEST_DIE
+#define SG_ERR_SUGGEST_SENSE SUGGEST_SENSE
+#define SG_ERR_SUGGEST_IS_OK SUGGEST_IS_OK
+#define SG_ERR_DRIVER_MASK DRIVER_MASK
+#define SG_ERR_SUGGEST_MASK SUGGEST_MASK
+
+#endif
diff --git a/sanei/sanei_config.c b/sanei/sanei_config.c
new file mode 100644
index 0000000..33ebbd7
--- /dev/null
+++ b/sanei/sanei_config.c
@@ -0,0 +1,462 @@
+/* sane - Scanner Access Now Easy.
+ Copyright (C) 1997 Jeffrey S. Freedman
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+
+ This file provides generic configuration support. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/param.h>
+
+#include "sane/sanei.h"
+#include "sane/sanei_config.h"
+
+#ifdef BACKEND_NAME
+#undef BACKEND_NAME
+#endif
+
+#define BACKEND_NAME sanei_config
+#include "sane/sanei_debug.h"
+
+#ifndef PATH_MAX
+# define PATH_MAX 1024
+#endif
+
+#if defined(HAVE_OS2_H)
+# define DIR_SEP ";"
+# define PATH_SEP '\\'
+#elif defined(HAVE_WINDOWS_H)
+# define DIR_SEP ";"
+# define PATH_SEP '\\'
+#else
+# define DIR_SEP ":"
+# define PATH_SEP '/'
+#endif
+
+#define DEFAULT_DIRS "." DIR_SEP STRINGIFY(PATH_SANE_CONFIG_DIR)
+
+#ifdef __BEOS__
+#include <FindDirectory.h>
+#endif
+
+static char *dir_list;
+
+const char *
+sanei_config_get_paths ()
+{
+#ifdef __BEOS__
+ char result[PATH_MAX];
+#endif
+ void *mem;
+ char *dlist;
+ size_t len;
+
+ if (!dir_list)
+ {
+ DBG_INIT();
+
+ dlist = getenv ("SANE_CONFIG_DIR");
+ if (dlist)
+ dir_list = strdup (dlist);
+#ifdef __BEOS__
+ /* ~/config/settings/SANE takes precedence over /etc/sane.d/ */
+ if (!dir_list)
+ {
+ if (find_directory(B_USER_SETTINGS_DIRECTORY, 0, true, result, PATH_MAX) == B_OK)
+ {
+ strcat(result,"/SANE");
+ strcat(result,DIR_SEP); /* do append the default ones */
+ dir_list = strdup (result);
+ }
+ }
+#endif
+ if (dir_list)
+ {
+ len = strlen (dir_list);
+ if ((len > 0) && (dir_list[len - 1] == DIR_SEP[0]))
+ {
+ /* append default search directories: */
+ mem = malloc (len + sizeof (DEFAULT_DIRS));
+ memcpy (mem, dir_list, len);
+ memcpy ((char *) mem + len, DEFAULT_DIRS, sizeof (DEFAULT_DIRS));
+ free (dir_list);
+ dir_list = mem;
+ }
+ }
+ else
+ {
+ /* Create a copy, since we might call free on it */
+ dir_list = strdup (DEFAULT_DIRS);
+ }
+ }
+ DBG (5, "sanei_config_get_paths: using config directories %s\n", dir_list);
+
+ return dir_list;
+}
+
+FILE *
+sanei_config_open (const char *filename)
+{
+ char *next, *dir, result[PATH_MAX];
+ const char *cfg_dir_list;
+ FILE *fp;
+ char *copy;
+
+ cfg_dir_list = sanei_config_get_paths ();
+ if (!cfg_dir_list)
+ {
+ DBG(2, "sanei_config_open: could not find config file `%s'\n", filename);
+ return NULL;
+ }
+
+ copy = strdup (cfg_dir_list);
+
+ for (next = copy; (dir = strsep (&next, DIR_SEP)) != 0; )
+ {
+ snprintf (result, sizeof (result), "%s%c%s", dir, PATH_SEP, filename);
+ DBG(4, "sanei_config_open: attempting to open `%s'\n", result);
+ fp = fopen (result, "r");
+ if (fp)
+ {
+ DBG(3, "sanei_config_open: using file `%s'\n", result);
+ break;
+ }
+ }
+ free (copy);
+
+ if (!fp)
+ DBG(2, "sanei_config_open: could not find config file `%s'\n", filename);
+
+ return fp;
+}
+
+const char *
+sanei_config_skip_whitespace (const char *str)
+{
+ while (str && *str && isspace (*str))
+ ++str;
+ return str;
+}
+
+const char *
+sanei_config_get_string (const char *str, char **string_const)
+{
+ const char *start;
+ size_t len;
+
+ str = sanei_config_skip_whitespace (str);
+
+ if (*str == '"')
+ {
+ start = ++str;
+ while (*str && *str != '"')
+ ++str;
+ len = str - start;
+ if (*str == '"')
+ ++str;
+ else
+ start = 0; /* final double quote is missing */
+ }
+ else
+ {
+ start = str;
+ while (*str && !isspace (*str))
+ ++str;
+ len = str - start;
+ }
+ if (start)
+ *string_const = strndup (start, len);
+ else
+ *string_const = 0;
+ return str;
+}
+
+char *
+sanei_config_read (char *str, int n, FILE *stream)
+{
+ char* rc;
+ char* start;
+ int len;
+
+ /* read line from stream */
+ rc = fgets( str, n, stream);
+ if (rc == NULL)
+ return NULL;
+
+ /* remove ending whitespaces */
+ len = strlen( str);
+ while( (0 < len) && (isspace( str[--len])) )
+ str[len] = '\0';
+
+ /* remove starting whitespaces */
+ start = str;
+ while( isspace( *start))
+ start++;
+
+ if (start != str)
+ do {
+ *str++ = *start++;
+ } while( *str);
+
+ return rc;
+}
+
+
+SANE_Status
+sanei_configure_attach (const char *config_file, SANEI_Config * config,
+ SANE_Status (*attach) (SANEI_Config * config,
+ const char *devname))
+{
+ SANE_Char line[PATH_MAX];
+ SANE_Char *token, *string;
+ SANE_Int len;
+ const char *lp, *lp2;
+ FILE *fp;
+ SANE_Status status = SANE_STATUS_GOOD;
+ int i, j, count;
+ void *value = NULL;
+ int size=0;
+ SANE_Bool found;
+ SANE_Word *wa;
+ SANE_Bool *ba;
+
+ DBG (3, "sanei_configure_attach: start\n");
+
+ /* open configuration file */
+ fp = sanei_config_open (config_file);
+ if (!fp)
+ {
+ DBG (2, "sanei_configure_attach: couldn't access %s\n", config_file);
+ DBG (3, "sanei_configure_attach: exit\n");
+ return SANE_STATUS_ACCESS_DENIED;
+ }
+
+ /* loop reading the configuration file, all line beginning by "option " are
+ * parsed for value to store in configuration structure, other line are
+ * used are device to try to attach
+ */
+ while (sanei_config_read (line, PATH_MAX, fp) && status == SANE_STATUS_GOOD)
+ {
+ /* skip white spaces at beginning of line */
+ lp = sanei_config_skip_whitespace (line);
+
+ /* skip empty lines */
+ if (*lp == 0)
+ continue;
+
+ /* skip comment line */
+ if (line[0] == '#')
+ continue;
+
+ len = strlen (line);
+
+ /* delete newline characters at end */
+ if (line[len - 1] == '\n')
+ line[--len] = '\0';
+
+ lp2 = lp;
+
+ /* to ensure maximum compatibility, we accept line like:
+ * option "option_name" "option_value"
+ * "option_name" "option_value"
+ * So we parse the line 2 time to find an option */
+ /* check if it is an option */
+ lp = sanei_config_get_string (lp, &token);
+ if (strncmp (token, "option", 6) == 0)
+ {
+ /* skip the "option" token */
+ free (token);
+ lp = sanei_config_get_string (lp, &token);
+ }
+
+ /* search for a matching descriptor */
+ i = 0;
+ found = SANE_FALSE;
+ while (config!=NULL && i < config->count && !found)
+ {
+ if (strcmp (config->descriptors[i]->name, token) == 0)
+ {
+ found = SANE_TRUE;
+ switch (config->descriptors[i]->type)
+ {
+ case SANE_TYPE_INT:
+ size=config->descriptors[i]->size;
+ value = malloc (size);
+ wa = (SANE_Word *) value;
+ count = config->descriptors[i]->size / sizeof (SANE_Word);
+ for (j = 0; j < count; j++)
+ {
+ lp = sanei_config_get_string (lp, &string);
+ if (string == NULL)
+ {
+ DBG (2,
+ "sanei_configure_attach: couldn't find a string to parse");
+ return SANE_STATUS_INVAL;
+ }
+ wa[j] = strtol (string, NULL, 0);
+ free (string);
+ }
+ break;
+ case SANE_TYPE_BOOL:
+ size=config->descriptors[i]->size;
+ value = malloc (size);
+ ba = (SANE_Bool *) value;
+ count = config->descriptors[i]->size / sizeof (SANE_Bool);
+ for (j = 0; j < count; j++)
+ {
+ lp = sanei_config_get_string (lp, &string);
+ if (string == NULL)
+ {
+ DBG (2,
+ "sanei_configure_attach: couldn't find a string to parse");
+ return SANE_STATUS_INVAL;
+ }
+ if ((strcmp (string, "1") == 0)
+ || (strcmp (string, "true") == 0))
+ {
+ ba[j] = SANE_TRUE;
+ }
+ else
+ {
+ if ((strcmp (string, "0") == 0)
+ || (strcmp (string, "false") == 0))
+ ba[j] = SANE_FALSE;
+ else
+ {
+ DBG (2,
+ "sanei_configure_attach: couldn't find a valid boolean value");
+ return SANE_STATUS_INVAL;
+ }
+ }
+ free (string);
+ }
+ break;
+ case SANE_TYPE_FIXED:
+ size=config->descriptors[i]->size;
+ value = malloc (size);
+ wa = (SANE_Word *) value;
+ count = config->descriptors[i]->size / sizeof (SANE_Word);
+ for (j = 0; j < count; j++)
+ {
+ lp = sanei_config_get_string (lp, &string);
+ if (string == NULL)
+ {
+ DBG (2,
+ "sanei_configure_attach: couldn't find a string to parse");
+ return SANE_STATUS_INVAL;
+ }
+ wa[j] = SANE_FIX(strtod (string, NULL));
+ free (string);
+ }
+ break;
+ case SANE_TYPE_STRING:
+ sanei_config_get_string (lp, &string);
+ if (string == NULL)
+ {
+ DBG (2,
+ "sanei_configure_attach: couldn't find a string value to parse");
+ return SANE_STATUS_INVAL;
+ }
+ value = string;
+ size=strlen(string)+1;
+ if(size>config->descriptors[i]->size)
+ {
+ size=config->descriptors[i]->size-1;
+ string[size]=0;
+ }
+ break;
+ default:
+ DBG (1,
+ "sanei_configure_attach: incorrect type %d for option %s, skipping option ...\n",
+ config->descriptors[i]->type,
+ config->descriptors[i]->name);
+ }
+
+ /* check decoded value */
+ status = sanei_check_value (config->descriptors[i], value);
+
+ /* if value OK, copy it in configuration struct */
+ if (status == SANE_STATUS_GOOD)
+ {
+ memcpy (config->values[i], value, size);
+ }
+ if (value != NULL)
+ {
+ free (value);
+ value = NULL;
+ }
+ }
+ if (status != SANE_STATUS_GOOD)
+ {
+ DBG (1,
+ "sanei_configure_attach: failed to parse option '%s', line '%s'\n",
+ token, line);
+ }
+ i++;
+ }
+ free (token);
+
+ /* not detected as an option, so we call the attach function
+ * with it */
+ if (!found && status == SANE_STATUS_GOOD)
+ {
+ /* if not an option, try to attach */
+ /* to avoid every backend to depend on scsi and usb functions
+ * we call back the backend for attach. In turn it will call
+ * sanei_usb_attach_matching_devices, sanei_config_attach_matching_devices
+ * or other. This means 2 callback functions per backend using this
+ * function. */
+ DBG (3, "sanei_configure_attach: trying to attach with '%s'\n",
+ lp2);
+ if(attach!=NULL)
+ attach (config, lp2);
+ }
+ }
+
+ fclose (fp);
+ DBG (3, "sanei_configure_attach: exit\n");
+ return status;
+}
diff --git a/sanei/sanei_constrain_value.c b/sanei/sanei_constrain_value.c
new file mode 100644
index 0000000..5043c20
--- /dev/null
+++ b/sanei/sanei_constrain_value.c
@@ -0,0 +1,304 @@
+/* sane - Scanner Access Now Easy.
+ Copyright (C) 1996, 1997 David Mosberger-Tang and Andreas Beck
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <sys/types.h>
+#include <stdlib.h>
+
+#include <stdio.h>
+
+#include <sane/sane.h>
+
+SANE_Status
+sanei_check_value (const SANE_Option_Descriptor * opt, void *value)
+{
+ const SANE_String_Const *string_list;
+ const SANE_Word *word_list;
+ int i, count;
+ const SANE_Range *range;
+ SANE_Word w, v, *array;
+ SANE_Bool *barray;
+ size_t len;
+
+ switch (opt->constraint_type)
+ {
+ case SANE_CONSTRAINT_RANGE:
+
+ /* single values are treated as arrays of length 1 */
+ array = (SANE_Word *) value;
+
+ /* compute number of elements */
+ if (opt->size > 0)
+ {
+ count = opt->size / sizeof (SANE_Word);
+ }
+ else
+ {
+ count = 1;
+ }
+
+ range = opt->constraint.range;
+ /* for each element of the array, we check according to the constraint */
+ for (i = 0; i < count; i++)
+ {
+ /* test for min and max */
+ if (array[i] < range->min || array[i] > range->max)
+ return SANE_STATUS_INVAL;
+
+ /* check quantization */
+ if (range->quant)
+ {
+ v =
+ (unsigned int) (array[i] - range->min +
+ range->quant / 2) / range->quant;
+ v = v * range->quant + range->min;
+ if (v != array[i])
+ return SANE_STATUS_INVAL;
+ }
+ }
+ break;
+
+ case SANE_CONSTRAINT_WORD_LIST:
+ w = *(SANE_Word *) value;
+ word_list = opt->constraint.word_list;
+ for (i = 1; w != word_list[i]; ++i)
+ if (i >= word_list[0])
+ return SANE_STATUS_INVAL;
+ break;
+
+ case SANE_CONSTRAINT_STRING_LIST:
+ string_list = opt->constraint.string_list;
+ len = strlen (value);
+
+ for (i = 0; string_list[i]; ++i)
+ if (strncmp (value, string_list[i], len) == 0
+ && len == strlen (string_list[i]))
+ return SANE_STATUS_GOOD;
+ return SANE_STATUS_INVAL;
+
+ case SANE_CONSTRAINT_NONE:
+ switch (opt->type)
+ {
+ case SANE_TYPE_BOOL:
+ /* single values are treated as arrays of length 1 */
+ array = (SANE_Word *) value;
+
+ /* compute number of elements */
+ if (opt->size > 0)
+ {
+ count = opt->size / sizeof (SANE_Bool);
+ }
+ else
+ {
+ count = 1;
+ }
+
+ barray = (SANE_Bool *) value;
+
+ /* test each boolean value in the array */
+ for (i = 0; i < count; i++)
+ {
+ if (barray[i] != SANE_TRUE && barray[i] != SANE_FALSE)
+ return SANE_STATUS_INVAL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ default:
+ break;
+ }
+ return SANE_STATUS_GOOD;
+}
+
+/**
+ * This function apply the constraint defined by the option descriptor
+ * to the given value, and update the info flags holder if needed. It
+ * return SANE_STATUS_INVAL if the constraint cannot be applied, else
+ * it returns SANE_STATUS_GOOD.
+ */
+SANE_Status
+sanei_constrain_value (const SANE_Option_Descriptor * opt, void *value,
+ SANE_Word * info)
+{
+ const SANE_String_Const *string_list;
+ const SANE_Word *word_list;
+ int i, k, num_matches, match;
+ const SANE_Range *range;
+ SANE_Word w, v, *array;
+ SANE_Bool b;
+ size_t len;
+
+ switch (opt->constraint_type)
+ {
+ case SANE_CONSTRAINT_RANGE:
+
+ /* single values are treated as arrays of length 1 */
+ array = (SANE_Word *) value;
+
+ /* compute number of elements */
+ if (opt->size > 0)
+ {
+ k = opt->size / sizeof (SANE_Word);
+ }
+ else
+ {
+ k = 1;
+ }
+
+ range = opt->constraint.range;
+ /* for each element of the array, we apply the constraint */
+ for (i = 0; i < k; i++)
+ {
+ /* constrain min */
+ if (array[i] < range->min)
+ {
+ array[i] = range->min;
+ if (info)
+ {
+ *info |= SANE_INFO_INEXACT;
+ }
+ }
+
+ /* constrain max */
+ if (array[i] > range->max)
+ {
+ array[i] = range->max;
+ if (info)
+ {
+ *info |= SANE_INFO_INEXACT;
+ }
+ }
+
+ /* quantization */
+ if (range->quant)
+ {
+ v =
+ (unsigned int) (array[i] - range->min +
+ range->quant / 2) / range->quant;
+ v = v * range->quant + range->min;
+ if (v != array[i])
+ {
+ array[i] = v;
+ if (info)
+ *info |= SANE_INFO_INEXACT;
+ }
+ }
+ }
+ break;
+
+ case SANE_CONSTRAINT_WORD_LIST:
+ /* If there is no exact match in the list, use the nearest value */
+ w = *(SANE_Word *) value;
+ word_list = opt->constraint.word_list;
+ for (i = 1, k = 1, v = abs (w - word_list[1]); i <= word_list[0]; i++)
+ {
+ SANE_Word vh;
+ if ((vh = abs (w - word_list[i])) < v)
+ {
+ v = vh;
+ k = i;
+ }
+ }
+ if (w != word_list[k])
+ {
+ *(SANE_Word *) value = word_list[k];
+ if (info)
+ *info |= SANE_INFO_INEXACT;
+ }
+ break;
+
+ case SANE_CONSTRAINT_STRING_LIST:
+ /* Matching algorithm: take the longest unique match ignoring
+ case. If there is an exact match, it is admissible even if
+ the same string is a prefix of a longer option name. */
+ string_list = opt->constraint.string_list;
+ len = strlen (value);
+
+ /* count how many matches of length LEN characters we have: */
+ num_matches = 0;
+ match = -1;
+ for (i = 0; string_list[i]; ++i)
+ if (strncasecmp (value, string_list[i], len) == 0
+ && len <= strlen (string_list[i]))
+ {
+ match = i;
+ if (len == strlen (string_list[i]))
+ {
+ /* exact match... */
+ if (strcmp (value, string_list[i]) != 0)
+ /* ...but case differs */
+ strcpy (value, string_list[match]);
+ return SANE_STATUS_GOOD;
+ }
+ ++num_matches;
+ }
+
+ if (num_matches > 1)
+ return SANE_STATUS_INVAL;
+ else if (num_matches == 1)
+ {
+ strcpy (value, string_list[match]);
+ return SANE_STATUS_GOOD;
+ }
+ return SANE_STATUS_INVAL;
+
+ case SANE_CONSTRAINT_NONE:
+ switch (opt->type)
+ {
+ case SANE_TYPE_BOOL:
+ b = *(SANE_Bool *) value;
+ if (b != SANE_TRUE && b != SANE_FALSE)
+ return SANE_STATUS_INVAL;
+ break;
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+ return SANE_STATUS_GOOD;
+}
diff --git a/sanei/sanei_init_debug.c b/sanei/sanei_init_debug.c
new file mode 100644
index 0000000..e471ae5
--- /dev/null
+++ b/sanei/sanei_init_debug.c
@@ -0,0 +1,141 @@
+/* sane - Scanner Access Now Easy.
+ Copyright (C) 1996, 1997 David Mosberger-Tang and Andreas Beck
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <string.h>
+#include <stdarg.h>
+#include <syslog.h>
+#ifdef HAVE_OS2_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#include <sys/stat.h>
+
+#ifdef HAVE_OS2_H
+# define INCL_DOS
+# include <os2.h>
+#endif
+
+#ifdef BACKEND_NAME
+#undef BACKEND_NAME
+#endif
+
+#define BACKEND_NAME sanei_debug
+#include "sane/sanei_debug.h"
+
+void
+sanei_init_debug (const char * backend, int * var)
+{
+ char ch, buf[256] = "SANE_DEBUG_";
+ const char * val;
+ unsigned int i;
+
+ *var = 0;
+
+ for (i = 11; (ch = backend[i - 11]) != 0; ++i)
+ {
+ if (i >= sizeof (buf) - 1)
+ break;
+ buf[i] = toupper(ch);
+ }
+ buf[i] = '\0';
+
+ val = getenv (buf);
+
+ if (!val)
+ return;
+
+ *var = atoi (val);
+
+ DBG (0, "Setting debug level of %s to %d.\n", backend, *var);
+}
+
+void
+sanei_debug_msg
+ (int level, int max_level, const char *be, const char *fmt, va_list ap)
+{
+ char *msg;
+
+ if (max_level >= level)
+ {
+#ifdef S_IFSOCK
+ if ( 1 == isfdtype(fileno(stderr), S_IFSOCK) )
+ {
+ msg = (char *)malloc (sizeof(char) * (strlen(be) + strlen(fmt) + 4));
+ if (msg == NULL)
+ {
+ syslog (LOG_DEBUG, "[sanei_debug] malloc() failed\n");
+ vsyslog (LOG_DEBUG, fmt, ap);
+ }
+ else
+ {
+ sprintf (msg, "[%s] %s", be, fmt);
+ vsyslog(LOG_DEBUG, msg, ap);
+ free (msg);
+ }
+ }
+ else
+#endif
+ {
+ fprintf (stderr, "[%s] ", be);
+ vfprintf (stderr, fmt, ap);
+ }
+
+ }
+}
+
+#ifdef NDEBUG
+void
+sanei_debug_ndebug (int level, const char *fmt, ...)
+{
+ /* this function is never called */
+}
+#endif
diff --git a/sanei/sanei_magic.c b/sanei/sanei_magic.c
new file mode 100644
index 0000000..3fe0968
--- /dev/null
+++ b/sanei/sanei_magic.c
@@ -0,0 +1,1398 @@
+/*
+ * sanei_magic - Image processing functions for despeckle, deskew, and autocrop
+
+ Copyright (C) 2009 m. allan noah
+
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <math.h>
+
+#ifdef BACKEND_NAME
+#undef BACKEND_NAME
+#endif
+
+#define BACKEND_NAME sanei_magic /* name of this module for debugging */
+
+#include <sane/sane.h>
+#include "sane/sanei_debug.h"
+#include "sane/sanei_magic.h"
+
+/* prototypes for utility functions defined at bottom of file */
+int * sanei_magic_getTransY (
+ const SANE_Parameters * params, int dpi, const SANE_Byte * buffer, int top);
+
+int * sanei_magic_getTransX (
+ const SANE_Parameters * params, int dpi, const SANE_Byte * buffer, int left);
+
+SANE_Status getTopEdge (int width, int height, int resolution,
+ int * buff, double * finSlope, int * finXInter, int * finYInter);
+
+SANE_Status getLeftEdge (int width, int height, int * top, int * bot,
+ double slope, int * finXInter, int * finYInter);
+
+SANE_Status getLine (int height, int width, int * buff,
+ int slopes, double minSlope, double maxSlope,
+ int offsets, int minOffset, int maxOffset,
+ double * finSlope, int * finOffset, int * finDensity);
+
+void
+sanei_magic_init( void )
+{
+ DBG_INIT();
+}
+
+/* find small spots and replace them with image background color */
+SANE_Status
+sanei_magic_despeck (SANE_Parameters * params, SANE_Byte * buffer,
+ SANE_Int diam)
+{
+
+ SANE_Status ret = SANE_STATUS_GOOD;
+
+ int pw = params->pixels_per_line;
+ int bw = params->bytes_per_line;
+ int h = params->lines;
+ int bt = bw*h;
+
+ int i,j,k,l,n;
+
+ DBG (10, "sanei_magic_despeck: start\n");
+
+ if(params->format == SANE_FRAME_RGB){
+
+ for(i=bw; i<bt-bw-(bw*diam); i+=bw){
+ for(j=1; j<pw-1-diam; j++){
+
+ int thresh = 255*3;
+ int outer[] = {0,0,0};
+ int hits = 0;
+
+ /* loop over rows and columns in window */
+ /* find darkest pixel */
+ for(k=0; k<diam; k++){
+ for(l=0; l<diam; l++){
+ int tmp = 0;
+
+ for(n=0; n<3; n++){
+ tmp += buffer[i + j*3 + k*bw + l*3 + n];
+ }
+
+ if(tmp < thresh)
+ thresh = tmp;
+ }
+ }
+
+ /* convert darkest pixel into a brighter threshold */
+ thresh = (thresh + 255*3 + 255*3)/3;
+
+ /*loop over rows and columns around window */
+ for(k=-1; k<diam+1; k++){
+ for(l=-1; l<diam+1; l++){
+
+ int tmp[3];
+
+ /* dont count pixels in the window */
+ if(k != -1 && k != diam && l != -1 && l != diam)
+ continue;
+
+ for(n=0; n<3; n++){
+ tmp[n] = buffer[i + j*3 + k*bw + l*3 + n];
+ outer[n] += tmp[n];
+ }
+ if(tmp[0]+tmp[1]+tmp[2] < thresh){
+ hits++;
+ break;
+ }
+ }
+ }
+
+ /*no hits, overwrite with avg surrounding color*/
+ if(!hits){
+
+ /* per channel replacement color */
+ for(n=0; n<3; n++){
+ outer[n] /= (4*diam + 4);
+ }
+
+ for(k=0; k<diam; k++){
+ for(l=0; l<diam; l++){
+ for(n=0; n<3; n++){
+ buffer[i + j*3 + k*bw + l*3 + n] = outer[n];
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ else if(params->format == SANE_FRAME_GRAY && params->depth == 8){
+ for(i=bw; i<bt-bw-(bw*diam); i+=bw){
+ for(j=1; j<pw-1-diam; j++){
+
+ int thresh = 255;
+ int outer = 0;
+ int hits = 0;
+
+ for(k=0; k<diam; k++){
+ for(l=0; l<diam; l++){
+ if(buffer[i + j + k*bw + l] < thresh)
+ thresh = buffer[i + j + k*bw + l];
+ }
+ }
+
+ /* convert darkest pixel into a brighter threshold */
+ thresh = (thresh + 255 + 255)/3;
+
+ /*loop over rows and columns around window */
+ for(k=-1; k<diam+1; k++){
+ for(l=-1; l<diam+1; l++){
+
+ int tmp = 0;
+
+ /* dont count pixels in the window */
+ if(k != -1 && k != diam && l != -1 && l != diam)
+ continue;
+
+ tmp = buffer[i + j + k*bw + l];
+
+ if(tmp < thresh){
+ hits++;
+ break;
+ }
+
+ outer += tmp;
+ }
+ }
+
+ /*no hits, overwrite with avg surrounding color*/
+ if(!hits){
+ /* replacement color */
+ outer /= (4*diam + 4);
+
+ for(k=0; k<diam; k++){
+ for(l=0; l<diam; l++){
+ buffer[i + j + k*bw + l] = outer;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ else if(params->format == SANE_FRAME_GRAY && params->depth == 1){
+ for(i=bw; i<bt-bw-(bw*diam); i+=bw){
+ for(j=1; j<pw-1-diam; j++){
+
+ int curr = 0;
+ int hits = 0;
+
+ for(k=0; k<diam; k++){
+ for(l=0; l<diam; l++){
+ curr += buffer[i + k*bw + (j+l)/8] >> (7-(j+l)%8) & 1;
+ }
+ }
+
+ if(!curr)
+ continue;
+
+ /*loop over rows and columns around window */
+ for(k=-1; k<diam+1; k++){
+ for(l=-1; l<diam+1; l++){
+
+ /* dont count pixels in the window */
+ if(k != -1 && k != diam && l != -1 && l != diam)
+ continue;
+
+ hits += buffer[i + k*bw + (j+l)/8] >> (7-(j+l)%8) & 1;
+
+ if(hits)
+ break;
+ }
+ }
+
+ /*no hits, overwrite with white*/
+ if(!hits){
+ for(k=0; k<diam; k++){
+ for(l=0; l<diam; l++){
+ buffer[i + k*bw + (j+l)/8] &= ~(1 << (7-(j+l)%8));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ else{
+ DBG (5, "sanei_magic_despeck: unsupported format/depth\n");
+ ret = SANE_STATUS_INVAL;
+ }
+
+ DBG (10, "sanei_magic_despeck: finish\n");
+ return ret;
+}
+
+/* find likely edges of media inside image background color */
+SANE_Status
+sanei_magic_findEdges(const SANE_Parameters * params, const SANE_Byte * buffer,
+ int dpiX, int dpiY, int * top, int * bot, int * left, int * right)
+{
+
+ SANE_Status ret = SANE_STATUS_GOOD;
+
+ int width = params->pixels_per_line;
+ int height = params->lines;
+
+ int * topBuf = NULL, * botBuf = NULL;
+ int * leftBuf = NULL, * rightBuf = NULL;
+
+ int topCount = 0, botCount = 0;
+ int leftCount = 0, rightCount = 0;
+
+ int i;
+
+ DBG (10, "sanei_magic_findEdges: start\n");
+
+ /* get buffers to find sides and bottom */
+ topBuf = sanei_magic_getTransY(params,dpiY,buffer,1);
+ if(!topBuf){
+ DBG (5, "sanei_magic_findEdges: no topBuf\n");
+ ret = SANE_STATUS_NO_MEM;
+ goto cleanup;
+ }
+
+ botBuf = sanei_magic_getTransY(params,dpiY,buffer,0);
+ if(!botBuf){
+ DBG (5, "sanei_magic_findEdges: no botBuf\n");
+ ret = SANE_STATUS_NO_MEM;
+ goto cleanup;
+ }
+
+ leftBuf = sanei_magic_getTransX(params,dpiX,buffer,1);
+ if(!leftBuf){
+ DBG (5, "sanei_magic_findEdges: no leftBuf\n");
+ ret = SANE_STATUS_NO_MEM;
+ goto cleanup;
+ }
+
+ rightBuf = sanei_magic_getTransX(params,dpiX,buffer,0);
+ if(!rightBuf){
+ DBG (5, "sanei_magic_findEdges: no rightBuf\n");
+ ret = SANE_STATUS_NO_MEM;
+ goto cleanup;
+ }
+
+ /* loop thru left and right lists, look for top and bottom extremes */
+ *top = height;
+ for(i=0; i<height; i++){
+ if(rightBuf[i] > leftBuf[i]){
+ if(*top > i){
+ *top = i;
+ }
+
+ topCount++;
+ if(topCount > 3){
+ break;
+ }
+ }
+ else{
+ topCount = 0;
+ *top = height;
+ }
+ }
+
+ *bot = -1;
+ for(i=height-1; i>=0; i--){
+ if(rightBuf[i] > leftBuf[i]){
+ if(*bot < i){
+ *bot = i;
+ }
+
+ botCount++;
+ if(botCount > 3){
+ break;
+ }
+ }
+ else{
+ botCount = 0;
+ *bot = -1;
+ }
+ }
+
+ /* could not find top/bot edges */
+ if(*top > *bot){
+ DBG (5, "sanei_magic_findEdges: bad t/b edges\n");
+ ret = SANE_STATUS_UNSUPPORTED;
+ goto cleanup;
+ }
+
+ /* loop thru top and bottom lists, look for l and r extremes
+ * NOTE: We dont look above the top or below the bottom found previously.
+ * This prevents issues with adf scanners that pad the image after the
+ * paper runs out (usually with white) */
+ DBG (5, "sanei_magic_findEdges: bb0:%d tb0:%d b:%d t:%d\n",
+ botBuf[0], topBuf[0], *bot, *top);
+
+ *left = width;
+ for(i=0; i<width; i++){
+ if(botBuf[i] > topBuf[i] && (botBuf[i]-10 < *bot || topBuf[i]+10 > *top)){
+ if(*left > i){
+ *left = i;
+ }
+
+ leftCount++;
+ if(leftCount > 3){
+ break;
+ }
+ }
+ else{
+ leftCount = 0;
+ *left = width;
+ }
+ }
+
+ *right = -1;
+ for(i=width-1; i>=0; i--){
+ if(botBuf[i] > topBuf[i] && (botBuf[i]-10 < *bot || topBuf[i]+10 > *top)){
+ if(*right < i){
+ *right = i;
+ }
+
+ rightCount++;
+ if(rightCount > 3){
+ break;
+ }
+ }
+ else{
+ rightCount = 0;
+ *right = -1;
+ }
+ }
+
+ /* could not find left/right edges */
+ if(*left > *right){
+ DBG (5, "sanei_magic_findEdges: bad l/r edges\n");
+ ret = SANE_STATUS_UNSUPPORTED;
+ goto cleanup;
+ }
+
+ DBG (15, "sanei_magic_findEdges: t:%d b:%d l:%d r:%d\n",
+ *top,*bot,*left,*right);
+
+ cleanup:
+ if(topBuf)
+ free(topBuf);
+ if(botBuf)
+ free(botBuf);
+ if(leftBuf)
+ free(leftBuf);
+ if(rightBuf)
+ free(rightBuf);
+
+ DBG (10, "sanei_magic_findEdges: finish\n");
+ return ret;
+}
+
+/* crop image to given size. updates params with new dimensions */
+SANE_Status
+sanei_magic_crop(SANE_Parameters * params, SANE_Byte * buffer,
+ int top, int bot, int left, int right)
+{
+
+ SANE_Status ret = SANE_STATUS_GOOD;
+
+ int bwidth = params->bytes_per_line;
+
+ int pixels = 0;
+ int bytes = 0;
+ unsigned char * line = NULL;
+ int pos = 0, i;
+
+ DBG (10, "sanei_magic_crop: start\n");
+
+ /*convert left and right to bytes, figure new byte and pixel width */
+ if(params->format == SANE_FRAME_RGB){
+ pixels = right-left;
+ bytes = pixels * 3;
+ left *= 3;
+ right *= 3;
+ }
+ else if(params->format == SANE_FRAME_GRAY && params->depth == 8){
+ pixels = right-left;
+ bytes = right-left;
+ }
+ else if(params->format == SANE_FRAME_GRAY && params->depth == 1){
+ left /= 8;
+ right = (right+7)/8;
+ bytes = right-left;
+ pixels = bytes * 8;
+ }
+ else{
+ DBG (5, "sanei_magic_crop: unsupported format/depth\n");
+ ret = SANE_STATUS_INVAL;
+ goto cleanup;
+ }
+
+ DBG (15, "sanei_magic_crop: l:%d r:%d p:%d b:%d\n",left,right,pixels,bytes);
+
+ line = malloc(bytes);
+ if(!line){
+ DBG (5, "sanei_magic_crop: no line\n");
+ ret = SANE_STATUS_NO_MEM;
+ goto cleanup;
+ }
+
+ for(i=top; i<bot; i++){
+ memcpy(line, buffer + i*bwidth + left, bytes);
+ memcpy(buffer + pos, line, bytes);
+ pos += bytes;
+ }
+
+ /* update the params struct with the new image size */
+ params->lines = bot-top;
+ params->pixels_per_line = pixels;
+ params->bytes_per_line = bytes;
+
+ cleanup:
+ if(line)
+ free(line);
+
+ DBG (10, "sanei_magic_crop: finish\n");
+ return ret;
+}
+
+/* find angle of media rotation against image background */
+SANE_Status
+sanei_magic_findSkew(const SANE_Parameters * params, const SANE_Byte * buffer,
+ int dpiX, int dpiY, int * centerX, int * centerY, double * finSlope)
+{
+ SANE_Status ret = SANE_STATUS_GOOD;
+
+ int pwidth = params->pixels_per_line;
+ int height = params->lines;
+
+ double TSlope = 0;
+ int TXInter = 0;
+ int TYInter = 0;
+ double TSlopeHalf = 0;
+ int TOffsetHalf = 0;
+
+ double LSlope = 0;
+ int LXInter = 0;
+ int LYInter = 0;
+ double LSlopeHalf = 0;
+ int LOffsetHalf = 0;
+
+ int rotateX = 0;
+ int rotateY = 0;
+
+ int * topBuf = NULL, * botBuf = NULL;
+
+ DBG (10, "sanei_magic_findSkew: start\n");
+
+ /* get buffers for edge detection */
+ topBuf = sanei_magic_getTransY(params,dpiY,buffer,1);
+ if(!topBuf){
+ DBG (5, "sanei_magic_findSkew: cant gTY\n");
+ ret = SANE_STATUS_NO_MEM;
+ goto cleanup;
+ }
+
+ botBuf = sanei_magic_getTransY(params,dpiY,buffer,0);
+ if(!botBuf){
+ DBG (5, "sanei_magic_findSkew: cant gTY\n");
+ ret = SANE_STATUS_NO_MEM;
+ goto cleanup;
+ }
+
+ /* find best top line */
+ ret = getTopEdge (pwidth, height, dpiY, topBuf,
+ &TSlope, &TXInter, &TYInter);
+ if(ret){
+ DBG(5,"sanei_magic_findSkew: gTE error: %d",ret);
+ goto cleanup;
+ }
+ DBG(15,"top: %04.04f %d %d\n",TSlope,TXInter,TYInter);
+
+ /* slope is too shallow, don't want to divide by 0 */
+ if(fabs(TSlope) < 0.0001){
+ DBG(15,"sanei_magic_findSkew: slope too shallow: %0.08f\n",TSlope);
+ ret = SANE_STATUS_UNSUPPORTED;
+ goto cleanup;
+ }
+
+ /* find best left line, perpendicular to top line */
+ LSlope = (double)-1/TSlope;
+ ret = getLeftEdge (pwidth, height, topBuf, botBuf, LSlope,
+ &LXInter, &LYInter);
+ if(ret){
+ DBG(5,"sanei_magic_findSkew: gLE error: %d",ret);
+ goto cleanup;
+ }
+ DBG(15,"sanei_magic_findSkew: left: %04.04f %d %d\n",LSlope,LXInter,LYInter);
+
+ /* find point about which to rotate */
+ TSlopeHalf = tan(atan(TSlope)/2);
+ TOffsetHalf = LYInter;
+ DBG(15,"sanei_magic_findSkew: top half: %04.04f %d\n",TSlopeHalf,TOffsetHalf);
+
+ LSlopeHalf = tan((atan(LSlope) + ((LSlope < 0)?-M_PI_2:M_PI_2))/2);
+ LOffsetHalf = - LSlopeHalf * TXInter;
+ DBG(15,"sanei_magic_findSkew: left half: %04.04f %d\n",LSlopeHalf,LOffsetHalf);
+
+ rotateX = (LOffsetHalf-TOffsetHalf) / (TSlopeHalf-LSlopeHalf);
+ rotateY = TSlopeHalf * rotateX + TOffsetHalf;
+ DBG(15,"sanei_magic_findSkew: rotate: %d %d\n",rotateX,rotateY);
+
+ *centerX = rotateX;
+ *centerY = rotateY;
+ *finSlope = TSlope;
+
+ cleanup:
+ if(topBuf)
+ free(topBuf);
+ if(botBuf)
+ free(botBuf);
+
+ DBG (10, "sanei_magic_findSkew: finish\n");
+ return ret;
+}
+
+/* function to do a simple rotation by a given slope, around
+ * a given point. The point can be outside of image to get
+ * proper edge alignment. Unused areas filled with bg color
+ * FIXME: Do in-place rotation to save memory */
+SANE_Status
+sanei_magic_rotate (SANE_Parameters * params, SANE_Byte * buffer,
+ int centerX, int centerY, double slope, int bg_color)
+{
+
+ SANE_Status ret = SANE_STATUS_GOOD;
+
+ double slopeRad = -atan(slope);
+ double slopeSin = sin(slopeRad);
+ double slopeCos = cos(slopeRad);
+
+ int pwidth = params->pixels_per_line;
+ int bwidth = params->bytes_per_line;
+ int height = params->lines;
+ int depth = 1;
+
+ unsigned char * outbuf;
+ int i, j, k;
+
+ DBG(10,"sanei_magic_rotate: start: %d %d\n",centerX,centerY);
+
+ outbuf = malloc(bwidth*height);
+ if(!outbuf){
+ DBG(15,"sanei_magic_rotate: no outbuf\n");
+ ret = SANE_STATUS_NO_MEM;
+ goto cleanup;
+ }
+
+ if(params->format == SANE_FRAME_RGB ||
+ (params->format == SANE_FRAME_GRAY && params->depth == 8)
+ ){
+
+ if(params->format == SANE_FRAME_RGB)
+ depth = 3;
+
+ memset(outbuf,bg_color,bwidth*height);
+
+ for (i=0; i<height; i++) {
+ int shiftY = centerY - i;
+
+ for (j=0; j<pwidth; j++) {
+ int shiftX = centerX - j;
+ int sourceX, sourceY;
+
+ sourceX = centerX - (int)(shiftX * slopeCos + shiftY * slopeSin);
+ if (sourceX < 0 || sourceX >= pwidth)
+ continue;
+
+ sourceY = centerY + (int)(-shiftY * slopeCos + shiftX * slopeSin);
+ if (sourceY < 0 || sourceY >= height)
+ continue;
+
+ for (k=0; k<depth; k++) {
+ outbuf[i*bwidth+j*depth+k]
+ = buffer[sourceY*bwidth+sourceX*depth+k];
+ }
+ }
+ }
+ }
+
+ else if(params->format == SANE_FRAME_GRAY && params->depth == 1){
+
+ if(bg_color)
+ bg_color = 0xff;
+
+ memset(outbuf,bg_color,bwidth*height);
+
+ for (i=0; i<height; i++) {
+ int shiftY = centerY - i;
+
+ for (j=0; j<pwidth; j++) {
+ int shiftX = centerX - j;
+ int sourceX, sourceY;
+
+ sourceX = centerX - (int)(shiftX * slopeCos + shiftY * slopeSin);
+ if (sourceX < 0 || sourceX >= pwidth)
+ continue;
+
+ sourceY = centerY + (int)(-shiftY * slopeCos + shiftX * slopeSin);
+ if (sourceY < 0 || sourceY >= height)
+ continue;
+
+ /* wipe out old bit */
+ outbuf[i*bwidth + j/8] &= ~(1 << (7-(j%8)));
+
+ /* fill in new bit */
+ outbuf[i*bwidth + j/8] |=
+ ((buffer[sourceY*bwidth + sourceX/8]
+ >> (7-(sourceX%8))) & 1) << (7-(j%8));
+ }
+ }
+ }
+ else{
+ DBG (5, "sanei_magic_rotate: unsupported format/depth\n");
+ ret = SANE_STATUS_INVAL;
+ goto cleanup;
+ }
+
+ memcpy(buffer,outbuf,bwidth*height);
+
+ cleanup:
+
+ if(outbuf)
+ free(outbuf);
+
+ DBG(10,"sanei_magic_rotate: finish\n");
+
+ return ret;
+}
+
+/* Utility functions, not used outside this file */
+
+/* Repeatedly call getLine to find the best range of slope and offset.
+ * Shift the ranges thru 4 different positions to avoid splitting data
+ * across multiple bins (false positive). Home-in on the most likely upper
+ * line of the paper inside the image. Return the 'best' edge. */
+SANE_Status
+getTopEdge(int width, int height, int resolution,
+ int * buff, double * finSlope, int * finXInter, int * finYInter)
+{
+ SANE_Status ret = SANE_STATUS_GOOD;
+
+ int slopes = 31;
+ int offsets = 31;
+ double maxSlope = 1;
+ double minSlope = -1;
+ int maxOffset = resolution/6;
+ int minOffset = -resolution/6;
+
+ double topSlope = 0;
+ int topOffset = 0;
+ int topDensity = 0;
+
+ int i,j;
+ int pass = 0;
+
+ DBG(10,"getTopEdge: start\n");
+
+ while(pass++ < 7){
+ double sStep = (maxSlope-minSlope)/slopes;
+ int oStep = (maxOffset-minOffset)/offsets;
+
+ double slope = 0;
+ int offset = 0;
+ int density = 0;
+ int go = 0;
+
+ topSlope = 0;
+ topOffset = 0;
+ topDensity = 0;
+
+ /* find lines 4 times with slightly moved params,
+ * to bypass binning errors, highest density wins */
+ for(i=0;i<2;i++){
+ double sStep2 = sStep*i/2;
+ for(j=0;j<2;j++){
+ int oStep2 = oStep*j/2;
+ ret = getLine(height,width,buff,slopes,minSlope+sStep2,maxSlope+sStep2,offsets,minOffset+oStep2,maxOffset+oStep2,&slope,&offset,&density);
+ if(ret){
+ DBG(5,"getTopEdge: getLine error %d\n",ret);
+ return ret;
+ }
+ DBG(15,"getTopEdge: %d %d %+0.4f %d %d\n",i,j,slope,offset,density);
+
+ if(density > topDensity){
+ topSlope = slope;
+ topOffset = offset;
+ topDensity = density;
+ }
+ }
+ }
+
+ DBG(15,"getTopEdge: ok %+0.4f %d %d\n",topSlope,topOffset,topDensity);
+
+ /* did not find anything promising on first pass,
+ * give up instead of fixating on some small, pointless feature */
+ if(pass == 1 && topDensity < width/5){
+ DBG(5,"getTopEdge: density too small %d %d\n",topDensity,width);
+ topOffset = 0;
+ topSlope = 0;
+ break;
+ }
+
+ /* if slope can zoom in some more, do so. */
+ if(sStep >= 0.0001){
+ minSlope = topSlope - sStep;
+ maxSlope = topSlope + sStep;
+ go = 1;
+ }
+
+ /* if offset can zoom in some more, do so. */
+ if(oStep){
+ minOffset = topOffset - oStep;
+ maxOffset = topOffset + oStep;
+ go = 1;
+ }
+
+ /* cannot zoom in more, bail out */
+ if(!go){
+ break;
+ }
+
+ DBG(15,"getTopEdge: zoom: %+0.4f %+0.4f %d %d\n",
+ minSlope,maxSlope,minOffset,maxOffset);
+ }
+
+ /* topOffset is in the center of the image,
+ * convert to x and y intercept */
+ if(topSlope != 0){
+ *finYInter = topOffset - topSlope * width/2;
+ *finXInter = *finYInter / -topSlope;
+ *finSlope = topSlope;
+ }
+ else{
+ *finYInter = 0;
+ *finXInter = 0;
+ *finSlope = 0;
+ }
+
+ DBG(10,"getTopEdge: finish\n");
+
+ return 0;
+}
+
+/* Loop thru a transition array, and use a simplified Hough transform
+ * to divide likely edges into a 2-d array of bins. Then weight each
+ * bin based on its angle and offset. Return the 'best' bin. */
+SANE_Status
+getLine (int height, int width, int * buff,
+ int slopes, double minSlope, double maxSlope,
+ int offsets, int minOffset, int maxOffset,
+ double * finSlope, int * finOffset, int * finDensity)
+{
+ SANE_Status ret = 0;
+
+ int ** lines = NULL;
+ int i, j;
+ int rise, run;
+ double slope;
+ int offset;
+ int sIndex, oIndex;
+ int hWidth = width/2;
+
+ double * slopeCenter = NULL;
+ int * slopeScale = NULL;
+ double * offsetCenter = NULL;
+ int * offsetScale = NULL;
+
+ int maxDensity = 1;
+ double absMaxSlope = fabs(maxSlope);
+ double absMinSlope = fabs(minSlope);
+ int absMaxOffset = abs(maxOffset);
+ int absMinOffset = abs(minOffset);
+
+ DBG(10,"getLine: start %+0.4f %+0.4f %d %d\n",
+ minSlope,maxSlope,minOffset,maxOffset);
+
+ /*silence compiler*/
+ height = height;
+
+ if(absMaxSlope < absMinSlope)
+ absMaxSlope = absMinSlope;
+
+ if(absMaxOffset < absMinOffset)
+ absMaxOffset = absMinOffset;
+
+ /* build an array of pretty-print values for slope */
+ slopeCenter = calloc(slopes,sizeof(double));
+ if(!slopeCenter){
+ DBG(5,"getLine: cant load slopeCenter\n");
+ ret = SANE_STATUS_NO_MEM;
+ goto cleanup;
+ }
+
+ /* build an array of scaling factors for slope */
+ slopeScale = calloc(slopes,sizeof(int));
+ if(!slopeScale){
+ DBG(5,"getLine: cant load slopeScale\n");
+ ret = SANE_STATUS_NO_MEM;
+ goto cleanup;
+ }
+
+ for(j=0;j<slopes;j++){
+
+ /* find central value of this 'bucket' */
+ slopeCenter[j] = (
+ (double)j*(maxSlope-minSlope)/slopes+minSlope
+ + (double)(j+1)*(maxSlope-minSlope)/slopes+minSlope
+ )/2;
+
+ /* scale value from the requested range into an inverted 100-1 range
+ * input close to 0 makes output close to 100 */
+ slopeScale[j] = 101 - fabs(slopeCenter[j])*100/absMaxSlope;
+ }
+
+ /* build an array of pretty-print values for offset */
+ offsetCenter = calloc(offsets,sizeof(double));
+ if(!offsetCenter){
+ DBG(5,"getLine: cant load offsetCenter\n");
+ ret = SANE_STATUS_NO_MEM;
+ goto cleanup;
+ }
+
+ /* build an array of scaling factors for offset */
+ offsetScale = calloc(offsets,sizeof(int));
+ if(!offsetScale){
+ DBG(5,"getLine: cant load offsetScale\n");
+ ret = SANE_STATUS_NO_MEM;
+ goto cleanup;
+ }
+
+ for(j=0;j<offsets;j++){
+
+ /* find central value of this 'bucket'*/
+ offsetCenter[j] = (
+ (double)j/offsets*(maxOffset-minOffset)+minOffset
+ + (double)(j+1)/offsets*(maxOffset-minOffset)+minOffset
+ )/2;
+
+ /* scale value from the requested range into an inverted 100-1 range
+ * input close to 0 makes output close to 100 */
+ offsetScale[j] = 101 - fabs(offsetCenter[j])*100/absMaxOffset;
+ }
+
+ /* build 2-d array of 'density', divided into slope and offset ranges */
+ lines = calloc(slopes, sizeof(int *));
+ if(!lines){
+ DBG(5,"getLine: cant load lines\n");
+ ret = SANE_STATUS_NO_MEM;
+ goto cleanup;
+ }
+
+ for(i=0;i<slopes;i++){
+ if(!(lines[i] = calloc(offsets, sizeof(int)))){
+ DBG(5,"getLine: cant load lines %d\n",i);
+ ret = SANE_STATUS_NO_MEM;
+ goto cleanup;
+ }
+ }
+
+ for(i=0;i<width;i++){
+ for(j=i+1;j<width && j<i+width/3;j++){
+
+ /*FIXME: check for invalid (min/max) values?*/
+ rise = buff[j] - buff[i];
+ run = j-i;
+
+ slope = (double)rise/run;
+ if(slope >= maxSlope || slope < minSlope)
+ continue;
+
+ /* offset in center of width, not y intercept! */
+ offset = slope * hWidth + buff[i] - slope * i;
+ if(offset >= maxOffset || offset < minOffset)
+ continue;
+
+ sIndex = (slope - minSlope) * slopes/(maxSlope-minSlope);
+ if(sIndex >= slopes)
+ continue;
+
+ oIndex = (offset - minOffset) * offsets/(maxOffset-minOffset);
+ if(oIndex >= offsets)
+ continue;
+
+ lines[sIndex][oIndex]++;
+ }
+ }
+
+ /* go thru array, and find most dense line (highest number) */
+ for(i=0;i<slopes;i++){
+ for(j=0;j<offsets;j++){
+ if(lines[i][j] > maxDensity)
+ maxDensity = lines[i][j];
+ }
+ }
+
+ DBG(15,"getLine: maxDensity %d\n",maxDensity);
+
+ *finSlope = 0;
+ *finOffset = 0;
+ *finDensity = 0;
+
+ /* go thru array, and scale densities to % of maximum, plus adjust for
+ * prefered (smaller absolute value) slope and offset */
+ for(i=0;i<slopes;i++){
+ for(j=0;j<offsets;j++){
+ lines[i][j] = lines[i][j] * slopeScale[i] * offsetScale[j] / maxDensity;
+ if(lines[i][j] > *finDensity){
+ *finDensity = lines[i][j];
+ *finSlope = slopeCenter[i];
+ *finOffset = offsetCenter[j];
+ }
+ }
+ }
+
+ if(0){
+ DBG(15,"offsetCenter: ");
+ for(j=0;j<offsets;j++){
+ DBG(15," %+04.0f",offsetCenter[j]);
+ }
+ DBG(15,"\n");
+
+ DBG(15,"offsetScale: ");
+ for(j=0;j<offsets;j++){
+ DBG(15," %04d",offsetScale[j]);
+ }
+ DBG(15,"\n");
+
+ for(i=0;i<slopes;i++){
+ DBG(15,"slope: %02d %+02.2f %03d:",i,slopeCenter[i],slopeScale[i]);
+ for(j=0;j<offsets;j++){
+ DBG(15,"% 5d",lines[i][j]/100);
+ }
+ DBG(15,"\n");
+ }
+ }
+
+ /* dont forget to cleanup */
+ cleanup:
+ for(i=0;i<slopes;i++){
+ if(lines[i])
+ free(lines[i]);
+ }
+ if(lines)
+ free(lines);
+ if(slopeCenter)
+ free(slopeCenter);
+ if(slopeScale)
+ free(slopeScale);
+ if(offsetCenter)
+ free(offsetCenter);
+ if(offsetScale)
+ free(offsetScale);
+
+ DBG(10,"getLine: finish\n");
+
+ return ret;
+}
+
+/* find the left side of paper by moving a line
+ * perpendicular to top slope across the image
+ * the 'left-most' point on the paper is the
+ * one with the smallest X intercept
+ * return x and y intercepts */
+SANE_Status
+getLeftEdge (int width, int height, int * top, int * bot,
+ double slope, int * finXInter, int * finYInter)
+{
+
+ int i;
+ int topXInter, topYInter;
+ int botXInter, botYInter;
+ int leftCount;
+
+ DBG(10,"getEdgeSlope: start\n");
+
+ topXInter = width;
+ topYInter = 0;
+ leftCount = 0;
+
+ for(i=0;i<width;i++){
+
+ if(top[i] < height){
+ int tyi = top[i] - (slope * i);
+ int txi = tyi/-slope;
+
+ if(topXInter > txi){
+ topXInter = txi;
+ topYInter = tyi;
+ }
+
+ leftCount++;
+ if(leftCount > 5){
+ break;
+ }
+ }
+ else{
+ topXInter = width;
+ topYInter = 0;
+ leftCount = 0;
+ }
+ }
+
+ botXInter = width;
+ botYInter = 0;
+ leftCount = 0;
+
+ for(i=0;i<width;i++){
+
+ if(bot[i] > -1){
+
+ int byi = bot[i] - (slope * i);
+ int bxi = byi/-slope;
+
+ if(botXInter > bxi){
+ botXInter = bxi;
+ botYInter = byi;
+ }
+
+ leftCount++;
+ if(leftCount > 5){
+ break;
+ }
+ }
+ else{
+ botXInter = width;
+ botYInter = 0;
+ leftCount = 0;
+ }
+ }
+
+ if(botXInter < topXInter){
+ *finXInter = botXInter;
+ *finYInter = botYInter;
+ }
+ else{
+ *finXInter = topXInter;
+ *finYInter = topYInter;
+ }
+
+ DBG(10,"getEdgeSlope: finish\n");
+
+ return 0;
+}
+
+/* Loop thru the image and look for first color change in each column.
+ * Return a malloc'd array. Caller is responsible for freeing. */
+int *
+sanei_magic_getTransY (
+ const SANE_Parameters * params, int dpi, const SANE_Byte * buffer, int top)
+{
+ int * buff;
+
+ int i, j, k;
+ int winLen = 9;
+
+ int width = params->pixels_per_line;
+ int height = params->lines;
+ int depth = 1;
+
+ /* defaults for bottom-up */
+ int firstLine = height-1;
+ int lastLine = -1;
+ int direction = -1;
+
+ DBG (10, "sanei_magic_getTransY: start\n");
+
+ /* override for top-down */
+ if(top){
+ firstLine = 0;
+ lastLine = height;
+ direction = 1;
+ }
+
+ /* build output and preload with impossible value */
+ buff = calloc(width,sizeof(int));
+ if(!buff){
+ DBG (5, "sanei_magic_getTransY: no buff\n");
+ return NULL;
+ }
+ for(i=0; i<width; i++)
+ buff[i] = lastLine;
+
+ /* load the buff array with y value for first color change from edge
+ * gray/color uses a different algo from binary/halftone */
+ if(params->format == SANE_FRAME_RGB ||
+ (params->format == SANE_FRAME_GRAY && params->depth == 8)
+ ){
+
+ if(params->format == SANE_FRAME_RGB)
+ depth = 3;
+
+ /* loop over all columns, find first transition */
+ for(i=0; i<width; i++){
+
+ int near = 0;
+ int far = 0;
+
+ /* load the near and far windows with repeated copy of first pixel */
+ for(k=0; k<depth; k++){
+ near += buffer[(firstLine*width+i) * depth + k];
+ }
+ near *= winLen;
+ far = near;
+
+ /* move windows, check delta */
+ for(j=firstLine+direction; j!=lastLine; j+=direction){
+
+ int farLine = j-winLen*2*direction;
+ int nearLine = j-winLen*direction;
+
+ if(farLine < 0 || farLine >= height){
+ farLine = firstLine;
+ }
+ if(nearLine < 0 || nearLine >= height){
+ nearLine = firstLine;
+ }
+
+ for(k=0; k<depth; k++){
+ far -= buffer[(farLine*width+i)*depth+k];
+ far += buffer[(nearLine*width+i)*depth+k];
+
+ near -= buffer[(nearLine*width+i)*depth+k];
+ near += buffer[(j*width+i)*depth+k];
+ }
+
+ /* significant transition */
+ if(abs(near - far) > 50*winLen*depth - near*40/255){
+ buff[i] = j;
+ break;
+ }
+ }
+ }
+ }
+
+ else if(params->format == SANE_FRAME_GRAY && params->depth == 1){
+
+ int near = 0;
+
+ for(i=0; i<width; i++){
+
+ /* load the near window with first pixel */
+ near = buffer[(firstLine*width+i)/8] >> (7-(i%8)) & 1;
+
+ /* move */
+ for(j=firstLine+direction; j!=lastLine; j+=direction){
+ if((buffer[(j*width+i)/8] >> (7-(i%8)) & 1) != near){
+ buff[i] = j;
+ break;
+ }
+ }
+ }
+ }
+
+ /* some other format? */
+ else{
+ DBG (5, "sanei_magic_getTransY: unsupported format/depth\n");
+ free(buff);
+ return NULL;
+ }
+
+ /* ignore transitions with few neighbors within .5 inch */
+ for(i=0;i<width-7;i++){
+ int sum = 0;
+ for(j=1;j<=7;j++){
+ if(abs(buff[i+j] - buff[i]) < dpi/2)
+ sum++;
+ }
+ if(sum < 2)
+ buff[i] = lastLine;
+ }
+
+ DBG (10, "sanei_magic_getTransY: finish\n");
+
+ return buff;
+}
+
+/* Loop thru the image height and look for first color change in each row.
+ * Return a malloc'd array. Caller is responsible for freeing. */
+int *
+sanei_magic_getTransX (
+ const SANE_Parameters * params, int dpi, const SANE_Byte * buffer, int left)
+{
+ int * buff;
+
+ int i, j, k;
+ int winLen = 9;
+
+ int bwidth = params->bytes_per_line;
+ int width = params->pixels_per_line;
+ int height = params->lines;
+ int depth = 1;
+
+ /* defaults for right-first */
+ int firstCol = width-1;
+ int lastCol = -1;
+ int direction = -1;
+
+ DBG (10, "sanei_magic_getTransX: start\n");
+
+ /* override for left-first*/
+ if(left){
+ firstCol = 0;
+ lastCol = width;
+ direction = 1;
+ }
+
+ /* build output and preload with impossible value */
+ buff = calloc(height,sizeof(int));
+ if(!buff){
+ DBG (5, "sanei_magic_getTransX: no buff\n");
+ return NULL;
+ }
+ for(i=0; i<height; i++)
+ buff[i] = lastCol;
+
+ /* load the buff array with x value for first color change from edge
+ * gray/color uses a different algo from binary/halftone */
+ if(params->format == SANE_FRAME_RGB ||
+ (params->format == SANE_FRAME_GRAY && params->depth == 8)
+ ){
+
+ if(params->format == SANE_FRAME_RGB)
+ depth = 3;
+
+ /* loop over all columns, find first transition */
+ for(i=0; i<height; i++){
+
+ int near = 0;
+ int far = 0;
+
+ /* load the near and far windows with repeated copy of first pixel */
+ for(k=0; k<depth; k++){
+ near += buffer[i*bwidth + k];
+ }
+ near *= winLen;
+ far = near;
+
+ /* move windows, check delta */
+ for(j=firstCol+direction; j!=lastCol; j+=direction){
+
+ int farCol = j-winLen*2*direction;
+ int nearCol = j-winLen*direction;
+
+ if(farCol < 0 || farCol >= width){
+ farCol = firstCol;
+ }
+ if(nearCol < 0 || nearCol >= width){
+ nearCol = firstCol;
+ }
+
+ for(k=0; k<depth; k++){
+ far -= buffer[i*bwidth + farCol*depth + k];
+ far += buffer[i*bwidth + nearCol*depth + k];
+
+ near -= buffer[i*bwidth + nearCol*depth + k];
+ near += buffer[i*bwidth + j*depth + k];
+ }
+
+ if(abs(near - far) > 50*winLen*depth - near*40/255){
+ buff[i] = j;
+ break;
+ }
+ }
+ }
+ }
+
+ else if (params->format == SANE_FRAME_GRAY && params->depth == 1){
+
+ int near = 0;
+
+ for(i=0; i<height; i++){
+
+ /* load the near window with first pixel */
+ near = buffer[i*bwidth + firstCol/8] >> (7-(firstCol%8)) & 1;
+
+ /* move */
+ for(j=firstCol+direction; j!=lastCol; j+=direction){
+ if((buffer[i*bwidth + j/8] >> (7-(j%8)) & 1) != near){
+ buff[i] = j;
+ break;
+ }
+ }
+ }
+ }
+
+ /* some other format? */
+ else{
+ DBG (5, "sanei_magic_getTransX: unsupported format/depth\n");
+ free(buff);
+ return NULL;
+ }
+
+ /* ignore transitions with few neighbors within .5 inch */
+ for(i=0;i<height-7;i++){
+ int sum = 0;
+ for(j=1;j<=7;j++){
+ if(abs(buff[i+j] - buff[i]) < dpi/2)
+ sum++;
+ }
+ if(sum < 2)
+ buff[i] = lastCol;
+ }
+
+ DBG (10, "sanei_magic_getTransX: finish\n");
+
+ return buff;
+}
+
diff --git a/sanei/sanei_pio.c b/sanei/sanei_pio.c
new file mode 100644
index 0000000..a2073be
--- /dev/null
+++ b/sanei/sanei_pio.c
@@ -0,0 +1,611 @@
+/* sane - Scanner Access Now Easy.
+ Copyright (C) 1998 Christian Bucher
+ Copyright (C) 1998 Kling & Hautzinger GmbH
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+
+ This file implements the bi-directional parallel-port
+ interface. */
+
+/*
+ RESTRICTIONS:
+
+ - This interface is very timing sensitive, be carefull with setting
+ debug levels.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef BACKEND_NAME
+#undef BACKEND_NAME
+#endif
+
+#define BACKEND_NAME sanei_pio
+#define STUBS
+#include "sane/sanei_debug.h"
+#include <fcntl.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#ifdef HAVE_SYS_IO_H
+# include <sys/io.h> /* use where available (glibc 2.x, for example) */
+#elif HAVE_ASM_IO_H
+# include <asm/io.h> /* ugly, but backwards compatible */
+#elif HAVE_SYS_HW_H
+# include <sys/hw.h>
+#elif defined(__i386__) && defined (__GNUC__)
+
+static __inline__ void
+outb (u_char value, u_long port)
+{
+ __asm__ __volatile__ ("outb %0,%1"::"a" (value), "d" ((u_short) port));
+}
+
+static __inline__ u_char
+inb (u_long port)
+{
+ u_char value;
+
+ __asm__ __volatile__ ("inb %1,%0":"=a" (value):"d" ((u_short) port));
+ return value;
+}
+
+#else
+# define IO_SUPPORT_MISSING
+#endif
+
+#include <sane/sane.h>
+#include "sane/sanei.h"
+#include "sane/sanei_pio.h"
+
+#if defined (HAVE_IOPERM) && !defined (IO_SUPPORT_MISSING)
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <time.h>
+
+#include <sane/saneopts.h>
+
+#define PORT_DEV "/dev/port"
+
+/* base 278 (lpt2)
+
+ ioport stat ctrl
+ offs 0 1 2
+ len 1 1 1 */
+
+/* Port definitions (`N' at end begin of label means negated signal) */
+
+#define PIO_IOPORT 0 /* rel. addr io port */
+
+#define PIO_STAT 1 /* rel. addr status port */
+#define PIO_STAT_BUSY (1<<7) /* BUSY Pin */
+#define PIO_STAT_NACKNLG (1<<6) /* ~ACKNLG Pin */
+
+#define PIO_CTRL 2 /* rel. addr control port */
+#define PIO_CTRL_IE (1<<5) /* Input enable */
+#define PIO_CTRL_IRQE (1<<4) /* enable IRQ */
+#define PIO_CTRL_DIR (1<<3) /* DIR pin, DIR=1 => out */
+#define PIO_CTRL_NINIT (1<<2) /* reset output */
+#define PIO_CTRL_FDXT (1<<1) /* Paper FEED (unused) */
+#define PIO_CTRL_NSTROBE (1<<0) /* strobe pin */
+
+#define PIO_APPLYRESET 2000 /* reset in 10us at init time */
+
+#define DL40 6
+#define DL50 7
+#define DL60 8
+#define DL61 9
+#define DL70 10
+#define DL71 11
+
+#ifdef NDEBUG
+# define DBG_INIT()
+#endif
+
+typedef struct
+ {
+ u_long base; /* i/o base address */
+ int fd; /* >= 0 when using /dev/port */
+ int max_time_seconds;/* forever if <= 0 */
+ u_int in_use; /* port in use? */
+ }
+PortRec, *Port;
+
+static PortRec port[] =
+ {
+ {0x378, -1, 0, 0},
+ {0x278, -1, 0, 0}
+ };
+
+extern int setuid (uid_t);
+
+static inline int pio_outb (const Port port, u_char val, u_long addr);
+static inline int pio_inb (const Port port, u_char * val, u_long addr);
+static inline int pio_wait (const Port port, u_char val, u_char mask);
+static inline void pio_ctrl (const Port port, u_char val);
+static inline void pio_delay (const Port port);
+static inline void pio_init (const Port port);
+static void pio_reset (const Port port);
+static int pio_write (const Port port, const u_char * buf, int n);
+static int pio_read (const Port port, u_char * buf, int n);
+static int pio_open (const char *dev, SANE_Status * status);
+
+static inline int
+pio_outb (const Port port, u_char val, u_long addr)
+{
+
+ if (-1 == port->fd)
+ outb (val, addr);
+ else
+ {
+ if (addr != (u_long)lseek (port->fd, addr, SEEK_SET))
+ return -1;
+ if (1 != write (port->fd, &val, 1))
+ return -1;
+ }
+ return 0;
+}
+
+static inline int
+pio_inb (const Port port, u_char * val, u_long addr)
+{
+
+ if (-1 == port->fd)
+ *val = inb (addr);
+ else
+ {
+ if (addr != (u_long)lseek (port->fd, addr, SEEK_SET))
+ return -1;
+ if (1 != read (port->fd, val, 1))
+ return -1;
+ }
+ return 0;
+}
+
+static inline int
+pio_wait (const Port port, u_char val, u_char mask)
+{
+ int stat = 0;
+ long poll_count = 0;
+ time_t start = time(NULL);
+
+ DBG (DL60, "wait on port 0x%03lx for %02x mask %02x\n",
+ port->base, (int) val, (int) mask);
+ DBG (DL61, " BUSY %s\n", (mask & PIO_STAT_BUSY) ?
+ (val & PIO_STAT_BUSY ? "on" : "off") : "-");
+ DBG (DL61, " NACKNLG %s\n",
+ (mask & PIO_STAT_NACKNLG) ? (val & PIO_STAT_NACKNLG ? "on" : "off")
+ : "-");
+ for (;;)
+ {
+ ++poll_count;
+ stat = inb (port->base + PIO_STAT);
+ if ((stat & mask) == (val & mask))
+ {
+ DBG (DL60, "got %02x after %ld tries\n", stat, poll_count);
+ DBG (DL61, " BUSY %s\n", stat & PIO_STAT_BUSY ? "on" : "off");
+ DBG (DL61, " NACKNLG %s\n",
+ stat & PIO_STAT_NACKNLG ? "on" : "off");
+
+ return stat;
+ }
+ if(poll_count>1000)
+ {
+ if ((port->max_time_seconds>0) && (time(NULL)-start >= port->max_time_seconds))
+ break;
+ usleep(1);
+ }
+
+ }
+ DBG (DL60, "got %02x aborting after %ld\n", stat, poll_count);
+ DBG (DL61, " BUSY %s\n", stat & PIO_STAT_BUSY ? "on" : "off");
+ DBG (DL61, " NACKNLG %s\n", stat & PIO_STAT_NACKNLG ? "on" : "off");
+ DBG (1, "polling time out, abort\n");
+ exit (-1);
+}
+
+static inline void
+pio_ctrl (const Port port, u_char val)
+{
+ DBG (DL60, "ctrl on port 0x%03lx %02x %02x\n",
+ port->base, (int) val, (int) val ^ PIO_CTRL_NINIT);
+
+ val ^= PIO_CTRL_NINIT;
+
+ DBG (DL61, " IE %s\n", val & PIO_CTRL_IE ? "on" : "off");
+ DBG (DL61, " IRQE %s\n", val & PIO_CTRL_IRQE ? "on" : "off");
+ DBG (DL61, " DIR %s\n", val & PIO_CTRL_DIR ? "on" : "off");
+ DBG (DL61, " NINIT %s\n", val & PIO_CTRL_NINIT ? "on" : "off");
+ DBG (DL61, " FDXT %s\n", val & PIO_CTRL_FDXT ? "on" : "off");
+ DBG (DL61, " NSTROBE %s\n", val & PIO_CTRL_NSTROBE ? "on" : "off");
+
+ outb (val, port->base + PIO_CTRL);
+
+ return;
+}
+
+static inline void
+pio_delay (const Port port)
+{
+ inb (port->base + PIO_STAT); /* delay */
+
+ return;
+}
+
+static inline void
+pio_init (const Port port)
+{
+ pio_ctrl (port, PIO_CTRL_IE);
+ return;
+}
+
+static void
+pio_reset (const Port port)
+{
+ int n;
+
+ DBG (DL40, "reset\n");
+
+ for (n = PIO_APPLYRESET; --n >= 0;)
+ {
+ outb ((PIO_CTRL_IE | PIO_CTRL_NINIT) ^ PIO_CTRL_NINIT,
+ port->base + PIO_CTRL);
+ }
+ pio_init (port);
+
+ DBG (DL40, "end reset\n");
+
+ return;
+}
+
+static int
+pio_write (const Port port, const u_char * buf, int n)
+{
+ int k;
+
+ DBG (DL40, "write\n");
+
+ pio_wait (port, PIO_STAT_BUSY, PIO_STAT_BUSY); /* busy */
+ pio_ctrl (port, PIO_CTRL_DIR | PIO_CTRL_IE); /* praeoutput */
+ pio_wait (port, PIO_STAT_NACKNLG, PIO_STAT_NACKNLG); /* acknlg */
+ pio_ctrl (port, PIO_CTRL_DIR); /* output */
+
+ for (k = 0; k < n; k++, buf++)
+ {
+ DBG (DL40, "write byte\n");
+#ifdef HANDSHAKE_BUSY
+ pio_wait (port, PIO_STAT_BUSY, PIO_STAT_BUSY); /* busy */
+#else
+ pio_wait (port, PIO_STAT_BUSY | PIO_STAT_NACKNLG,
+ PIO_STAT_BUSY | PIO_STAT_NACKNLG); /* busyack */
+#endif
+ DBG (DL60, "out %02x\n", (int) *buf);
+
+ outb (*buf, port->base + PIO_IOPORT);
+
+ pio_delay (port);
+ pio_delay (port);
+ pio_delay (port);
+ pio_ctrl (port, PIO_CTRL_DIR | PIO_CTRL_NSTROBE); /* outputstrobe */
+
+ pio_delay (port);
+ pio_delay (port);
+ pio_delay (port);
+ pio_ctrl (port, PIO_CTRL_DIR); /* output */
+
+ pio_delay (port);
+ pio_delay (port);
+ pio_delay (port);
+
+ DBG (DL40, "end write byte\n");
+ }
+
+#ifdef HANDSHAKE_BUSY
+ pio_wait (port, PIO_STAT_BUSY, PIO_STAT_BUSY); /* busy */
+#else
+ pio_wait (port, PIO_STAT_BUSY | PIO_STAT_NACKNLG,
+ PIO_STAT_BUSY | PIO_STAT_NACKNLG); /* busyack */
+#endif
+
+ pio_ctrl (port, PIO_CTRL_DIR | PIO_CTRL_IE); /* praeoutput */
+ DBG (DL40, "end write\n");
+ return k;
+}
+
+static int
+pio_read (const Port port, u_char * buf, int n)
+{
+ int k;
+
+ DBG (DL40, "read\n");
+
+ pio_wait (port, PIO_STAT_BUSY, PIO_STAT_BUSY); /* busy */
+ pio_ctrl (port, PIO_CTRL_IE); /* input */
+
+ for (k = 0; k < n; k++, buf++)
+ {
+ DBG (DL40, "read byte\n");
+
+#ifdef HANDSHAKE_BUSY
+ pio_wait (port, PIO_STAT_BUSY, PIO_STAT_BUSY); /* busy */
+#else
+ pio_wait (port, PIO_STAT_BUSY, PIO_STAT_BUSY | PIO_STAT_NACKNLG);
+ /* busynack */
+#endif
+ pio_ctrl (port, PIO_CTRL_IE | PIO_CTRL_NSTROBE); /* inputstrobe */
+
+ pio_delay (port);
+ pio_delay (port);
+ pio_delay (port);
+ pio_ctrl (port, PIO_CTRL_IE); /* input */
+#ifdef HANDSHAKE_BUSY
+ pio_wait (port, PIO_STAT_BUSY, PIO_STAT_BUSY); /* busy */
+#else
+ pio_wait (port, PIO_STAT_BUSY, PIO_STAT_BUSY | PIO_STAT_NACKNLG);
+ /* busynack */
+#endif
+
+ *buf = inb (port->base + PIO_IOPORT);
+ DBG (DL60, "in %02x\n", (int) *buf);
+ DBG (DL40, "end read byte\n");
+ }
+
+ pio_wait (port, PIO_STAT_BUSY, PIO_STAT_BUSY); /* busy */
+ pio_ctrl (port, PIO_CTRL_IE); /* input */
+ DBG (DL40, "end read\n");
+ return k;
+}
+
+/*
+ Open the device, <dev> must contain a valid port number (as string).
+ */
+
+static int
+pio_open (const char *dev, SANE_Status * status)
+{
+ static int first_time = 1;
+ u_long base;
+ int n;
+
+ if (first_time)
+ {
+ first_time = 0;
+
+ DBG_INIT ();
+ /* set root uid */
+ if (0 > setuid (0))
+ {
+ DBG (1, "sanei_pio_open: setuid failed: errno = %d\n", errno);
+ *status = SANE_STATUS_INVAL;
+ return -1;
+ }
+ }
+ /* read port number */
+ {
+ char *end;
+
+ base = strtol (dev, &end, 0);
+
+ if ((end == dev) || *end)
+ {
+ DBG (1, "sanei_pio_open: `%s' is not a valid port number\n", dev);
+ *status = SANE_STATUS_INVAL;
+ return -1;
+ }
+ }
+
+ if (0 == base)
+ {
+ DBG (1, "sanei_pio_open: 0x%03lx is not a valid base address\n", base);
+ *status = SANE_STATUS_INVAL;
+ return -1;
+ }
+
+ for (n = 0; n < NELEMS (port); n++)
+ if (port[n].base == base)
+ break;
+
+ if (NELEMS (port) <= n)
+ {
+ DBG (1, "sanei_pio_open: 0x%03lx is not a valid base address\n", base);
+ *status = SANE_STATUS_INVAL;
+ return -1;
+ }
+
+ if (port[n].in_use)
+ {
+ DBG (1, "sanei_pio_open: port 0x%03lx is already in use\n", base);
+ *status = SANE_STATUS_DEVICE_BUSY;
+ return -1;
+ }
+ port[n].base = base;
+ port[n].fd = -1;
+ port[n].max_time_seconds = 10;
+ port[n].in_use = 1;
+
+ if (ioperm (port[n].base, 3, 1))
+ {
+ DBG (1, "sanei_pio_open: cannot get io privilege for port 0x%03lx\n",
+ port[n].base);
+ *status = SANE_STATUS_IO_ERROR;
+ return -1;
+ }
+
+ pio_reset (&port[n]);
+
+ *status = SANE_STATUS_GOOD;
+ return n;
+}
+
+SANE_Status
+sanei_pio_open (const char *dev, int *fdp)
+{
+ SANE_Status status;
+
+ *fdp = pio_open (dev, &status);
+ return status;
+}
+
+void
+sanei_pio_close (int fd)
+{
+ Port p = port + fd;
+
+ if ((0 > fd) && (NELEMS (port) <= fd))
+ return;
+
+ if (!p->in_use)
+ return;
+
+ if (-1 != p->fd)
+ {
+ close (p->fd);
+ p->fd = -1;
+ }
+
+ p->in_use = 0;
+
+ return;
+}
+
+int
+sanei_pio_read (int fd, u_char * buf, int n)
+{
+ if ((0 > fd) && (NELEMS (port) <= fd))
+ return -1;
+
+ if (!port[fd].in_use)
+ return -1;
+
+ return pio_read (&port[fd], buf, n);
+}
+
+int
+sanei_pio_write (int fd, const u_char * buf, int n)
+{
+ if ((0 > fd) && (NELEMS (port) <= fd))
+ return -1;
+
+ if (!port[fd].in_use)
+ return -1;
+
+ return pio_write (&port[fd], buf, n);
+}
+
+#else /* !HAVE_IOPERM */
+
+#ifdef __BEOS__
+
+#include <fcntl.h>
+
+SANE_Status
+sanei_pio_open (const char *dev, int *fdp)
+{
+ int fp;
+
+ /* open internal parallel port */
+ fp=open("/dev/parallel/parallel1",O_RDWR);
+
+ *fdp=fp;
+ if(fp<0) return SANE_STATUS_INVAL;
+ return(SANE_STATUS_GOOD);
+}
+
+
+void
+sanei_pio_close (int fd)
+{
+ close(fd);
+ return;
+}
+
+int
+sanei_pio_read (int fd, u_char * buf, int n)
+{
+ return(read(fd,buf,n));
+}
+
+int
+sanei_pio_write (int fd, const u_char * buf, int n)
+{
+ return(write(fd,buf,n));
+}
+
+#else /* !__BEOS__ */
+
+SANE_Status
+sanei_pio_open (const char *dev, int *fdp)
+{
+ *fdp = -1;
+ return SANE_STATUS_INVAL;
+}
+
+
+void
+sanei_pio_close (int fd)
+{
+ return;
+}
+
+int
+sanei_pio_read (int fd, u_char * buf, int n)
+{
+ return -1;
+}
+
+int
+sanei_pio_write (int fd, const u_char * buf, int n)
+{
+ return -1;
+}
+#endif /* __BEOS__ */
+
+#endif /* !HAVE_IOPERM */
diff --git a/sanei/sanei_scsi.c b/sanei/sanei_scsi.c
new file mode 100644
index 0000000..5e85dcb
--- /dev/null
+++ b/sanei/sanei_scsi.c
@@ -0,0 +1,6180 @@
+/* sane - Scanner Access Now Easy.
+ Copyright (C) 1996, 1997 David Mosberger-Tang
+ Copyright (C) 2003 Frank Zago
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+
+ This file provides a generic SCSI interface. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/types.h>
+
+#define STUBBED_INTERFACE 0
+#define LINUX_INTERFACE 1
+#define BSD_INTERFACE 2
+#define HPUX_INTERFACE 3
+#define OPENSTEP_INTERFACE 4
+#define DECUNIX_INTERFACE 5
+#define SCO_OS5_INTERFACE 6
+#define IRIX_INTERFACE 7
+#define SOLARIS_INTERFACE 8
+#define SOLARIS_SG_INTERFACE 9
+#define OS2_INTERFACE 10
+#define AIX_GSC_INTERFACE 11
+#define DOMAINOS_INTERFACE 12
+#define FREEBSD_CAM_INTERFACE 13
+#define SYSVR4_INTERFACE 14
+#define SCO_UW71_INTERFACE 15
+#define SOLARIS_USCSI_INTERFACE 16
+#define MACOSX_INTERFACE 17
+#define WIN32_INTERFACE 18
+
+#ifdef HAVE_RESMGR
+# include <resmgr.h>
+#endif
+
+#if defined (HAVE_SCSI_SG_H)
+# define USE LINUX_INTERFACE
+# include <scsi/sg.h>
+#elif defined (HAVE__USR_SRC_LINUX_INCLUDE_SCSI_SG_H)
+# define USE LINUX_INTERFACE
+# include "/usr/src/linux/include/scsi/sg.h"
+#elif defined (HAVE_SYS_SCSICMD)
+# define USE SCSO_OS5_INTERFACE
+# include <sys/scsi.h>
+# include <sys/scsicmd.h>
+#elif defined (HAVE_CAMLIB_H)
+# define USE FREEBSD_CAM_INTERFACE
+# include <stdio.h> /* there is a bug in scsi_all.h */
+# include <cam/cam.h>
+# include <cam/cam_ccb.h>
+# include <cam/scsi/scsi_message.h>
+# include <cam/scsi/scsi_pass.h>
+# include <camlib.h>
+#elif defined (HAVE_SYS_SCSIIO_H)
+# define USE BSD_INTERFACE
+# include <sys/scsiio.h>
+# ifdef HAVE_SCSI_H
+# include <scsi.h>
+# endif
+#elif defined (HAVE_BSD_DEV_SCSIREG_H)
+# define USE OPENSTEP_INTERFACE
+# include <bsd/dev/scsireg.h>
+#elif defined (HAVE_IO_CAM_CAM_H)
+# define USE DECUNIX_INTERFACE
+# include <io/common/iotypes.h>
+# include <io/cam/cam.h>
+# include <io/cam/dec_cam.h>
+# include <io/cam/uagt.h>
+# include <io/cam/scsi_all.h>
+#elif defined (HAVE_SYS_DSREQ_H)
+# define USE IRIX_INTERFACE
+# include <sys/dsreq.h>
+# include <invent.h>
+#elif defined (HAVE_SYS_SCSI_H)
+# include <sys/scsi.h>
+# ifdef HAVE_SYS_SDI_COMM_H
+# ifdef HAVE_SYS_PASSTHRUDEF_H
+# define USE SCO_UW71_INTERFACE
+# include <sys/scsi.h>
+# include <sys/sdi_edt.h>
+# include <sys/sdi.h>
+# include <sys/passthrudef.h>
+# else
+# define USE SYSVR4_INTERFACE /* Unixware 2.x tested */
+# define HAVE_SYSV_DRIVER
+# include <sys/sdi_edt.h>
+# include <sys/sdi_comm.h>
+# endif
+# else
+# ifdef SCTL_READ
+# define USE HPUX_INTERFACE
+# else
+# ifdef HAVE_GSCDDS_H
+# define USE AIX_GSC_INTERFACE
+# include <gscdds.h>
+# else
+ /* This happens for AIX without gsc and possibly other platforms... */
+# endif
+# endif
+# endif
+#elif defined (HAVE_OS2_H)
+# define USE OS2_INTERFACE
+# define INCL_DOSFILEMGR
+# define INCL_DOS
+# define INCL_DOSDEVICES
+# define INCL_DOSDEVIOCTL
+# define INCL_DOSMEMMGR
+# include <os2.h>
+# include "os2_srb.h"
+#elif defined (HAVE_SYS_SCSI_SGDEFS_H)
+# define USE SOLARIS_SG_INTERFACE
+# include <sys/scsi/sgdefs.h>
+#elif defined (HAVE_SYS_SCSI_TARGETS_SCGIO_H)
+# define USE SOLARIS_INTERFACE
+# define SOL2
+# include <sys/scsi/targets/scgio.h>
+#elif defined (HAVE_SYS_SCSI_SCSI_H)
+ /*
+ * the "offical" solaris uscsi(7I) interface; comes last, so that users
+ * installing the SCG/SG driver can still use these generic scsi interfaces
+ */
+# define USE SOLARIS_USCSI_INTERFACE
+# define SOL2
+# include <sys/scsi/scsi.h>
+#elif defined (HAVE_APOLLO_SCSI_H)
+# define USE DOMAINOS_INTERFACE
+# include <signal.h> /* Only used for signal name for KillDomainServer */
+# include <apollo/base.h>
+# include <apollo/ec2.h>
+# include <apollo/error.h>
+# include <apollo/ms.h>
+# include <apollo/mutex.h>
+# include <apollo/scsi.h>
+# include <apollo/time.h>
+# include "sanei_DomainOS.h"
+#elif defined (HAVE_IOKIT_CDB_IOSCSILIB_H) || \
+ defined (HAVE_IOKIT_SCSI_SCSICOMMANDOPERATIONCODES_H) || \
+ defined (HAVE_IOKIT_SCSI_COMMANDS_SCSICOMMANDOPERATIONCODES_H)
+# define USE MACOSX_INTERFACE
+# include <CoreFoundation/CoreFoundation.h>
+# include <IOKit/IOKitLib.h>
+# ifdef HAVE_IOKIT_CDB_IOSCSILIB_H
+# include <IOKit/IOCFPlugIn.h>
+# include <IOKit/cdb/IOSCSILib.h>
+# endif
+# ifdef HAVE_IOKIT_SCSI_SCSICOMMANDOPERATIONCODES_H
+/* The def of VERSION causes problems in the following include files */
+# undef VERSION
+# include <IOKit/scsi/SCSICmds_INQUIRY_Definitions.h>
+# include <IOKit/scsi/SCSICommandOperationCodes.h>
+# include <IOKit/scsi/SCSITaskLib.h>
+# else
+# ifdef HAVE_IOKIT_SCSI_COMMANDS_SCSICOMMANDOPERATIONCODES_H
+/* The def of VERSION causes problems in the following include files */
+# undef VERSION
+# include <IOKit/scsi-commands/SCSICmds_INQUIRY_Definitions.h>
+# include <IOKit/scsi-commands/SCSICommandOperationCodes.h>
+# include <IOKit/scsi-commands/SCSITaskLib.h>
+# endif
+# endif
+#elif defined (HAVE_WINDOWS_H)
+# define USE WIN32_INTERFACE
+# include <windows.h>
+# include <ddk/scsi.h>
+# include <ddk/ntddscsi.h>
+#endif
+
+#ifndef USE
+# define USE STUBBED_INTERFACE
+#endif
+
+#if USE == LINUX_INTERFACE
+# include <dirent.h>
+#endif
+
+#include "sane/sanei.h"
+#include "sane/sanei_config.h"
+#include "sane/sanei_scsi.h"
+
+#ifdef BACKEND_NAME
+#undef BACKEND_NAME
+#endif
+
+#define BACKEND_NAME sanei_scsi
+#include "sane/sanei_debug.h"
+
+#if USE == DECUNIX_INTERFACE
+static int cam_fd = -1; /* used for SCSI CAM based interfaces */
+#endif
+
+#if USE == SOLARIS_INTERFACE || USE == SOLARIS_USCSI_INTERFACE
+static int unit_ready (int fd);
+#endif
+
+#ifdef SG_BIG_BUFF
+# define MAX_DATA SG_BIG_BUFF
+#endif
+
+#if USE == SYSVR4_INTERFACE
+# define MAX_DATA 56*1024 /* don't increase or kernel will dump
+ * tested with adsl, adsa and umax backend
+ * it depends on the lowend scsi
+ * drivers . But the most restriction
+ * is in the UNIXWARE KERNEL witch do
+ * not allow more then 64kB DMA transfers */
+static char lastrcmd[16]; /* hold command block of last read command */
+#endif
+
+#if USE == OPENSTEP_INTERFACE
+# define MAX_DATA (120*1024)
+#endif
+
+#if USE == IRIX_INTERFACE
+# define MAX_DATA (256*1024)
+#endif
+
+#if USE == FREEBSD_CAM_INTERFACE
+# define MAX_DATA (DFLTPHYS - PAGE_SIZE)
+#endif
+
+#if USE == SOLARIS_INTERFACE
+# define MAX_DATA (128*1024)
+#endif
+
+#if USE == SOLARIS_SG_INTERFACE
+# define MAX_DATA (128*1024)
+#endif
+
+#if USE == SOLARIS_USCSI_INTERFACE
+# define MAX_DATA (64*1024)
+#endif
+
+#if USE == OS2_INTERFACE
+# define MAX_DATA (64*1024)
+#endif
+
+#if USE == MACOSX_INTERFACE
+# define MAX_DATA (128*1024)
+#endif
+
+
+#ifndef MAX_DATA
+# define MAX_DATA (32*1024)
+#endif
+
+#ifdef SG_SET_TIMEOUT
+# ifdef _SC_CLK_TCK
+# define GNU_HZ sysconf(_SC_CLK_TCK)
+# else
+# ifdef HZ
+# define GNU_HZ HZ
+# else
+# ifdef CLOCKS_PER_SEC
+# define GNU_HZ CLOCKS_PER_SEC
+# endif
+# endif
+# endif
+#endif
+
+/* default timeout value: 120 seconds */
+static int sane_scsicmd_timeout = 120;
+int sanei_scsi_max_request_size = MAX_DATA;
+#if USE == LINUX_INTERFACE
+/* the following #defines follow Douglas Gilbert's sample code
+ to maintain run time compatibility with the old and the
+ new SG driver for Linux
+*/
+#include "linux_sg3_err.h" /* contains several definitions of error codes */
+#ifndef SG_SET_COMMAND_Q
+#define SG_SET_COMMAND_Q 0x2271
+#endif
+#ifndef SG_SET_RESERVED_SIZE
+#define SG_SET_RESERVED_SIZE 0x2275
+#endif
+#ifndef SG_GET_RESERVED_SIZE
+#define SG_GET_RESERVED_SIZE 0x2272
+#endif
+#ifndef SG_GET_SCSI_ID
+#define SG_GET_SCSI_ID 0x2276
+#else
+#define SG_GET_SCSI_ID_FOUND
+#endif
+#ifndef SG_GET_VERSION_NUM
+#define SG_GET_VERSION_NUM 0x2282
+#endif
+#ifndef SG_NEXT_CMD_LEN
+#define SG_NEXT_CMD_LEN 0x2283
+#endif
+
+#ifndef SCSIBUFFERSIZE
+#define SCSIBUFFERSIZE (128 * 1024)
+#endif
+
+/* the struct returned by the SG ioctl call SG_GET_SCSI_ID changed
+ from version 2.1.34 to 2.1.35, and we need the informations from
+ the field s_queue_depth, which was introduced in 2.1.35.
+ To get this file compiling also with older versions of sg.h, the
+ struct is re-defined here.
+*/
+typedef struct xsg_scsi_id
+{
+ int host_no; /* as in "scsi<n>" where 'n' is one of 0, 1, 2 etc */
+ int channel;
+ int scsi_id; /* scsi id of target device */
+ int lun;
+ int scsi_type; /* TYPE_... defined in scsi/scsi.h */
+ short h_cmd_per_lun; /* host (adapter) maximum commands per lun */
+ short d_queue_depth; /* device (or adapter) maximum queue length */
+ int unused1; /* probably find a good use, set 0 for now */
+ int unused2; /* ditto */
+}
+SG_scsi_id;
+
+typedef struct req
+{
+ struct req *next;
+ int fd;
+ u_int running:1, done:1;
+ SANE_Status status;
+ size_t *dst_len;
+ void *dst;
+/* take the definition of the ioctl parameter SG_IO as a
+ compiler flag if the new SG driver is available
+*/
+ union
+ {
+ struct
+ {
+ struct sg_header hdr;
+ /* Make sure this is the last element, the real size is
+ SG_BIG_BUFF and machine dependant */
+ u_int8_t data[1];
+ }
+ cdb;
+#ifdef SG_IO
+/* at present, Linux's SCSI system limits the sense buffer to 16 bytes
+ which is definitely too small. Hoping that this will change at some time,
+ let's set the sense buffer size to 64.
+*/
+#define SENSE_MAX 64
+#define MAX_CDB 12
+ struct
+ {
+ struct sg_io_hdr hdr;
+ u_char sense_buffer[SENSE_MAX];
+ u_int8_t data[1];
+ }
+ sg3;
+#endif
+ }
+ sgdata;
+}
+req;
+
+typedef struct Fdparms
+{
+ int sg_queue_used, sg_queue_max;
+ size_t buffersize;
+ req *sane_qhead, *sane_qtail, *sane_free_list;
+}
+fdparms;
+
+#endif
+
+#if USE == FREEBSD_CAM_INTERFACE
+# define CAM_MAXDEVS 128
+struct cam_device *cam_devices[CAM_MAXDEVS] = { NULL };
+#endif
+
+static struct
+{
+ u_int in_use:1; /* is this fd_info in use? */
+ u_int fake_fd:1; /* is this a fake file descriptor? */
+ u_int bus, target, lun; /* nexus info; used for some interfaces only */
+ SANEI_SCSI_Sense_Handler sense_handler;
+ void *sense_handler_arg;
+ void *pdata; /* platform-specific data */
+}
+ *fd_info;
+
+static u_char cdb_sizes[8] = {
+ 6, 10, 10, 12, 12, 12, 10, 10
+};
+#define CDB_SIZE(opcode) cdb_sizes[(((opcode) >> 5) & 7)]
+
+
+#if USE == DOMAINOS_INTERFACE
+
+/*
+ This includes the server code. Most of these routines are private to the
+ actual server. The only public ones are:
+ sanei_DomainOS_init Used to initialize the server
+ DomainErrorCheck A common error handling routine
+ */
+
+#include "sanei_DomainOS.c"
+
+int ServerInitialized = 0;
+pid_t ServerPID;
+struct DomainServerCommon *com;
+long CommandTriggerValue[2];
+ec2_$ptr_t CommandAcceptedPtr[2];
+long ResultTriggerValue[2];
+ec2_$ptr_t ResultReadyPtr[2];
+time_$clock_t Wait16S = { 64, 0 }; /* Delay of about 16 Seconds */
+
+
+/* This function is registered as an exit function. It's purpose is
+ to make sure that the Domain SANE Server is stopped. It tries to
+ send an Exit command, and if that fails, it will send SIGQUIT to
+ the server. It will also unmap the common area before it
+ returns. */
+static void
+KillDomainServer (void)
+{
+ static boolean GotTheLock;
+ static status_$t status;
+ static pinteger index;
+
+ DBG (1, "Asking Domain SANE Server to exit\n");
+ /* First, try to send a command to exit */
+ if (GotTheLock = mutex_$lock (&com->CommandLock, Wait16S))
+ {
+ /* Set the wait time to 16 Seconds (units are 4uS) */
+ com->opcode = Exit;
+ CommandTriggerValue[0] = ec2_$read (com->CommandAccepted) + 1;
+ ec2_$advance (&com->CommandAvailable, &status);
+ DomainErrorCheck (status, "Can't advance CommandAvailable EC");
+ /* For this wait, we want to allow a timeout as well */
+ CommandTriggerValue[1] = (ec2_$read (*CommandAcceptedPtr[1])
+ + DomainECWaitConstant);
+ index = ec2_$wait_svc (CommandAcceptedPtr, CommandTriggerValue, 2,
+ &status);
+ DomainErrorCheck (status,
+ "Error waiting on Exit command acceptance EC");
+ /* Release the lock */
+ mutex_$unlock (&com->CommandLock);
+ if (index == 1)
+ DBG (1, "Domain SANE Server responded to exit request\n");
+ else
+ DBG (1, "Domain SANE Server did not respond to exit request\n");
+ }
+ else
+ DBG (0, "Could not get mutex lock for killing server\n");
+ if ((!GotTheLock) || (index != 1))
+ {
+ /* If we get here, then we never got the mutex lock, or we timed out
+ waiting for an Exit command ack. */
+ /* It's now time to be brutal with the server */
+ DBG (1, "Sending QUIT signal to Domain SANE Server\n");
+ kill (ServerPID, SIGQUIT);
+ }
+ /* unmap the common area */
+ ms_$unmap (com, sizeof (struct DomainServerCommon), &status);
+ DomainErrorCheck (status, "Error unmapping common area");
+}
+#endif /* USE == DOMAINOS_INTERFACE */
+
+
+#if USE == OS2_INTERFACE
+
+/* Driver info: */
+static HFILE driver_handle = 0; /* file handle for device driver */
+static PVOID aspi_buf = 0; /* Big data buffer locked by driver. */
+static int aspi_ref_count = 0; /* # of fds using ASPI */
+static SRB *PSRBlock = 0; /* SCSI Request Block */
+static char tmpAspi[MAXPATHLEN]; /* scsi chain scan */
+#define INQUIRY 0x12
+#define set_inquiry_return_size(icb,val) icb[0x04]=val
+#define IN_periph_devtype_cpu 0x03
+#define IN_periph_devtype_scanner 0x06
+#define get_inquiry_vendor(in, buf) strncpy(buf, in + 0x08, 0x08)
+#define get_inquiry_product(in, buf) strncpy(buf, in + 0x10, 0x010)
+#define get_inquiry_version(in, buf) strncpy(buf, in + 0x20, 0x04)
+#define get_inquiry_periph_devtype(in) (in[0] & 0x1f)
+#define get_inquiry_additional_length(in) in[0x04]
+#define set_inquiry_length(out,n) out[0x04]=n-5
+
+/* Open OS2 ASPI driver.
+
+ Output: 0 if error, which is reported. */
+static int
+open_aspi (void)
+{
+ ULONG rc;
+ ULONG ActionTaken;
+ USHORT lockSegmentReturn;
+ unsigned long cbreturn = 0;
+ unsigned long cbParam = 0;
+ int i, num_adapters; /* no. of scsi adapters installed */
+
+ char *devtypes[] = {
+ "disk", "tape", "printer", "processor", "CD-writer",
+ "CD-drive", "scanner", "optical-drive", "jukebox",
+ "communicator"
+ };
+ FILE *tmp;
+
+ if (driver_handle)
+ {
+ aspi_ref_count++; /* increment internal usage counter */
+ return 1; /* Already open. */
+ }
+ aspi_buf = _tcalloc (sanei_scsi_max_request_size, 1);
+ if (aspi_buf == NULL)
+ {
+ DBG (1, "sanei_scsi_open_aspi: _tcalloc aspi_buf failed");
+ return 0;
+ }
+
+ PSRBlock = _tcalloc (sizeof (SRB), 1);
+ if (PSRBlock == NULL)
+ {
+ DBG (1, "sanei_scsi_open_aspi: _tcalloc PSRBlock failed");
+ return 0;
+ }
+
+ rc = DosOpen ((PSZ) "aspirou$", /* open driver */
+ &driver_handle,
+ &ActionTaken,
+ 0,
+ 0,
+ FILE_OPEN,
+ OPEN_SHARE_DENYREADWRITE | OPEN_ACCESS_READWRITE, NULL);
+ if (rc)
+ {
+ /* opening failed -> return false */
+ DBG (1, "open_aspi: opening failed.\n");
+ return 0;
+ }
+
+ /* Lock aspi_buf. */
+ rc = DosDevIOCtl (driver_handle, 0x92, 0x04, /* pass aspi_buf pointer */
+ (void *) aspi_buf, sizeof (PVOID), /* to driver */
+ &cbParam, (void *) &lockSegmentReturn,
+ sizeof (USHORT), &cbreturn);
+ if (rc || lockSegmentReturn)
+ {
+ /* DosDevIOCtl failed */
+ DBG (1, "sanei_scsi_open_aspi: Can't lock buffer. rc= %lu \n", rc);
+ return 0;
+ }
+
+ /* query number of installed adapters */
+ memset (PSRBlock, 0, sizeof (SRB));
+ PSRBlock->cmd = SRB_Inquiry; /* host adapter inquiry */
+
+ PSRBlock->ha_num = 0; /* host adapter number */
+
+ PSRBlock->flags = 0; /* no flags set */
+
+ rc = DosDevIOCtl (driver_handle, 0x92, 0x02,
+ (void *) PSRBlock, sizeof (SRB), &cbParam,
+ (void *) PSRBlock, sizeof (SRB), &cbreturn);
+ num_adapters = PSRBlock->u.inq.num_ha;
+
+ DBG (1, "OS/2: installed adapters %d\n", num_adapters);
+ DBG (1, "OS/2: ASPI manager is '%s'\n", PSRBlock->u.inq.aspimgr_id);
+ DBG (1, "OS/2: host adapter is '%s'\n", PSRBlock->u.inq.host_id);
+ DBG (1, "OS/2: unique id is '%s'\n", PSRBlock->u.inq.unique_id);
+
+ strcpy (tmpAspi, "asXXXXXX");
+ mktemp (tmpAspi);
+ DBG (2, "open_aspi: open temporary file '%s'\n", tmpAspi);
+ tmp = fopen (tmpAspi, "w");
+ if (!tmp)
+ { /* can't open tmp file */
+
+ DBG (1, "open_aspi: Can't open temporary file.\n");
+ return 0;
+ }
+
+ /* scan all installed adapters */
+ for (i = 0; i < num_adapters; i++)
+ {
+ int id;
+ /* query adapter name */
+ memset (PSRBlock, 0, sizeof (SRB));
+ PSRBlock->cmd = SRB_Inquiry; /* host adapter inquiry */
+
+ PSRBlock->ha_num = i; /* host adapter number */
+
+ PSRBlock->flags = 0; /* no flags set */
+
+ rc = DosDevIOCtl (driver_handle, 0x92, 0x02,
+ (void *) PSRBlock, sizeof (SRB), &cbParam,
+ (void *) PSRBlock, sizeof (SRB), &cbreturn);
+ DBG (1, "OS/2: adapter#%02d '%s'\n", i, PSRBlock->u.inq.host_id);
+
+ /* scan scsi chain (need 15 for wide?) */
+ for (id = 0; id < 7; id++)
+ {
+ unsigned char len;
+ char vendor[9];
+ char product[17];
+ char version[5];
+ char *pp;
+
+ memset (PSRBlock, 0, sizeof (SRB));
+ PSRBlock->cmd = SRB_Device; /* get device type */
+
+ PSRBlock->ha_num = i; /* host adapter number */
+
+ PSRBlock->flags = 0; /* no flags set */
+
+ PSRBlock->u.dev.target = id; /* target id */
+
+ PSRBlock->u.dev.lun = 0; /* target LUN */
+
+ rc = DosDevIOCtl (driver_handle, 0x92, 0x02,
+ (void *) PSRBlock, sizeof (SRB), &cbParam,
+ (void *) PSRBlock, sizeof (SRB), &cbreturn);
+ DBG (1, "OS/2: id#%02d status=%02xh\n",
+ id, PSRBlock->status);
+
+ /* skip if device not connected */
+ if (PSRBlock->status == SRB_BadDevice)
+ continue;
+
+ DBG (1, "OS/2: type is '%s'\n",
+ PSRBlock->u.dev.devtype < sizeof (devtypes) / sizeof (char *)?
+ devtypes[PSRBlock->u.dev.devtype] : "unknown device");
+
+ /* query adapter string id */
+ memset (PSRBlock, 0, sizeof (SRB));
+ PSRBlock->cmd = SRB_Command; /* execute SCSI command */
+
+ PSRBlock->ha_num = i; /* host adapter number */
+ PSRBlock->flags = SRB_Read | SRB_Post; /* data transfer, posting */
+ PSRBlock->u.cmd.target = id; /* Target SCSI ID */
+ PSRBlock->u.cmd.lun = 0; /* Target SCSI LUN */
+ PSRBlock->u.cmd.data_len = 5; /* # of bytes transferred */
+ PSRBlock->u.cmd.sense_len = 32; /* length of sense buffer */
+ PSRBlock->u.cmd.data_ptr = NULL; /* pointer to data buffer */
+ PSRBlock->u.cmd.link_ptr = NULL; /* pointer to next SRB */
+ PSRBlock->u.cmd.cdb_len = 6; /* SCSI command length */
+ PSRBlock->u.cmd.cdb_st[0] = INQUIRY; /* inquiry command */
+ PSRBlock->u.cmd.cdb_st[1] = 0; /* ?? length */
+ PSRBlock->u.cmd.cdb_st[2] = 0; /* transfer length MSB */
+ PSRBlock->u.cmd.cdb_st[3] = 0; /* transfer length */
+ PSRBlock->u.cmd.cdb_st[4] = 5; /* transfer length LSB */
+ PSRBlock->u.cmd.cdb_st[5] = 0;
+ rc = DosDevIOCtl (driver_handle, 0x92, 0x02,
+ (void *) PSRBlock, sizeof (SRB), &cbParam,
+ (void *) PSRBlock, sizeof (SRB), &cbreturn);
+ len = ((char *) aspi_buf)[4]; /* additional length */
+
+ /* query id string */
+ memset (PSRBlock, 0, sizeof (SRB));
+ PSRBlock->cmd = SRB_Command; /* execute SCSI command */
+ PSRBlock->ha_num = i; /* host adapter number */
+ PSRBlock->flags = SRB_Read | SRB_Post; /* data transfer, posting */
+ PSRBlock->u.cmd.target = id; /* Target SCSI ID */
+ PSRBlock->u.cmd.lun = 0; /* Target SCSI LUN */
+ PSRBlock->u.cmd.data_len = 5 + len; /* # of bytes transferred */
+ PSRBlock->u.cmd.sense_len = 32; /* length of sense buffer */
+ PSRBlock->u.cmd.data_ptr = NULL; /* pointer to data buffer */
+ PSRBlock->u.cmd.link_ptr = NULL; /* pointer to next SRB */
+ PSRBlock->u.cmd.cdb_len = 6; /* SCSI command length */
+ PSRBlock->u.cmd.cdb_st[0] = 0x12; /* inquiry command */
+ PSRBlock->u.cmd.cdb_st[1] = 0; /* ?? length */
+ PSRBlock->u.cmd.cdb_st[2] = 0; /* transfer length MSB */
+ PSRBlock->u.cmd.cdb_st[3] = 0; /* transfer length */
+ PSRBlock->u.cmd.cdb_st[4] = 5 + len; /* transfer length LSB */
+ PSRBlock->u.cmd.cdb_st[5] = 0;
+ rc = DosDevIOCtl (driver_handle, 0x92, 0x02,
+ (void *) PSRBlock, sizeof (SRB), &cbParam,
+ (void *) PSRBlock, sizeof (SRB), &cbreturn);
+ DBG (1, "OS/2 '%s'\n", (char *) aspi_buf + 8);
+ /* write data */
+ get_inquiry_vendor ((char *) aspi_buf, vendor);
+ get_inquiry_product ((char *) aspi_buf, product);
+ get_inquiry_version ((char *) aspi_buf, version);
+
+ pp = &vendor[7];
+ vendor[8] = '\0';
+ while (pp >= vendor && (*pp == ' ' || *pp >= 127))
+ *pp-- = '\0';
+
+ pp = &product[15];
+ product[16] = '\0';
+ while (pp >= product && (*pp == ' ' || *pp >= 127))
+ *pp-- = '\0';
+
+ pp = product;
+ do
+ {
+ if (isspace ((int) *pp))
+ *pp = '_';
+ }
+ while (*++pp);
+
+ pp = &version[3];
+ version[4] = '\0';
+ while (pp >= version && (*pp == ' ' || *(pp - 1) >= 127))
+ *pp-- = '\0';
+ fprintf (tmp, "Vendor: %s ", vendor);
+ fprintf (tmp, "Model: %s ", product);
+ fprintf (tmp, "Rev: %s ", version);
+ fprintf (tmp, "scsi %d Channel: 0 Id: %d Lun: 0\n", i, id);
+ }
+ }
+ DBG (2, "open_aspi: close temporary file '%s'\n", tmpAspi);
+ fclose (tmp);
+
+ aspi_ref_count++; /* increment internal usage counter */
+
+ return 1;
+}
+
+/* Close driver and free everything. */
+
+static void
+close_aspi (void)
+{
+ aspi_ref_count--; /* decrement internal usage counter */
+
+ if (aspi_ref_count)
+ return; /* wait for usage==0 */
+
+ if (driver_handle) /* Close driver. */
+ DosClose (driver_handle);
+ driver_handle = 0;
+ if (aspi_buf) /* Free buffer. */
+ _tfree (aspi_buf);
+ aspi_buf = 0;
+
+ if (PSRBlock)
+ _tfree (PSRBlock);
+ PSRBlock = 0;
+
+ errno = 0;
+ if (unlink (tmpAspi)) /* remove scsi descriptions */
+ DBG (2, "OS/2: error#%d while removing temporary '%s'\n", errno, tmpAspi);
+ strcpy (tmpAspi, "");
+
+ DBG (1, "OS/2: ASPI closed\n");
+}
+
+#endif /* USE_OS2_INTERFACE */
+
+static int num_alloced = 0;
+
+#if USE == LINUX_INTERFACE
+
+static int sg_version = 0;
+
+static SANE_Status
+get_max_buffer_size (const char *file)
+{
+ int fd = -1;
+ int buffersize = SCSIBUFFERSIZE, i;
+ size_t len;
+ char *cc, *cc1, buf[32];
+
+#ifdef HAVE_RESMGR
+ fd = rsm_open_device(file, O_RDWR);
+#endif
+
+ if (fd == -1)
+ fd = open (file, O_RDWR);
+
+ if (fd > 0)
+ {
+ cc = getenv ("SANE_SG_BUFFERSIZE");
+ if (cc)
+ {
+ i = strtol (cc, &cc1, 10);
+ if (cc != cc1 && i >= 32768)
+ buffersize = i;
+ }
+
+ ioctl (fd, SG_SET_RESERVED_SIZE, &buffersize);
+ if (0 == ioctl (fd, SG_GET_RESERVED_SIZE, &buffersize))
+ {
+ if (buffersize < sanei_scsi_max_request_size)
+ sanei_scsi_max_request_size = buffersize;
+ close (fd);
+ DBG (4, "get_max_buffer_size for %s: %i\n", file,
+ sanei_scsi_max_request_size);
+ return SANE_STATUS_GOOD;
+ }
+ else
+ {
+ close (fd);
+ /* ioctl not available: we have the old SG driver */
+ fd = open ("/proc/sys/kernel/sg-big-buff", O_RDONLY);
+ if (fd > 0 && (len = read (fd, buf, sizeof (buf) - 1)) > 0)
+ {
+ buf[len] = '\0';
+ sanei_scsi_max_request_size = atoi (buf);
+ close (fd);
+ }
+ else
+ sanei_scsi_max_request_size = buffersize < SG_BIG_BUFF ?
+ buffersize : SG_BIG_BUFF;
+ return SANE_STATUS_IO_ERROR;
+ }
+ }
+ else
+ return SANE_STATUS_GOOD;
+}
+
+
+SANE_Status
+sanei_scsi_open_extended (const char *dev, int *fdp,
+ SANEI_SCSI_Sense_Handler handler,
+ void *handler_arg, int *buffersize)
+#else
+
+SANE_Status
+sanei_scsi_open (const char *dev, int *fdp,
+ SANEI_SCSI_Sense_Handler handler, void *handler_arg)
+#endif
+{
+ u_int bus = 0, target = 0, lun = 0, fake_fd = 0;
+ char *real_dev = 0;
+ void *pdata = 0;
+ char *cc, *cc1;
+ int fd, i;
+#if USE == LINUX_INTERFACE
+ static int first_time = 1;
+#elif USE == MACOSX_INTERFACE
+ UInt8 *guid;
+ int len;
+ u_int d;
+#endif
+
+ cc = getenv ("SANE_SCSICMD_TIMEOUT");
+ if (cc)
+ {
+ i = strtol (cc, &cc1, 10);
+ /* 20 minutes are hopefully enough as a timeout value ;) */
+ if (cc != cc1 && i > 0 && i <= 1200)
+ {
+ sane_scsicmd_timeout = i;
+ }
+ else
+ {
+ DBG (1,
+ "sanei_scsi_open: timeout value must be between 1 and 1200 seconds\n");
+ }
+ }
+
+ DBG_INIT ();
+
+#if USE == LINUX_INTERFACE
+ if (first_time)
+ {
+ first_time = 0;
+
+ /* Try to determine a reliable value for sanei_scsi_max_request_size:
+
+ With newer versions of the SG driver, check the available buffer
+ size by opening all SG device files belonging to a scanner,
+ issue the ioctl calls for setting and reading the reserved
+ buffer size, and take the smallest value.
+
+ For older version of the SG driver, which don't support variable
+ buffer size, try to read /proc/sys/kernel/sg-big-biff ; if
+ this fails (SG driver too old, or loaded as a module), use
+ SG_BIG_BUFF
+ */
+
+ sanei_scsi_max_request_size = SCSIBUFFERSIZE;
+ cc = getenv ("SANE_SG_BUFFERSIZE");
+ if (cc)
+ {
+ i = strtol (cc, &cc1, 10);
+ if (cc != cc1 && i >= 32768)
+ sanei_scsi_max_request_size = i;
+ }
+ sanei_scsi_find_devices (0, 0, "Scanner", -1, -1, -1, -1,
+ get_max_buffer_size);
+ sanei_scsi_find_devices (0, 0, "Processor", -1, -1, -1, -1,
+ get_max_buffer_size);
+ DBG (4, "sanei_scsi_open: sanei_scsi_max_request_size=%d bytes\n",
+ sanei_scsi_max_request_size);
+ }
+#endif
+
+#if USE == OS2_INTERFACE
+ if (sscanf (dev, "b%ut%ul%u", &bus, &target, &lun) != 3)
+ {
+ DBG (1, "sanei_scsi_open: device name %s is not valid\n", dev);
+ return SANE_STATUS_INVAL;
+ }
+ if (!open_aspi ())
+ {
+ /* Open driver if necessary. */
+ close_aspi ();
+ return SANE_STATUS_INVAL;
+ }
+
+ /* Find fake fd. */
+ for (fd = 0; fd < num_alloced; ++fd)
+ if (!fd_info[fd].in_use)
+ break;
+ fake_fd = 1;
+#elif USE == DECUNIX_INTERFACE
+ {
+ UAGT_CAM_SCAN cam_scan;
+
+ if (sscanf (dev, "b%dt%dl%d", &bus, &target, &lun) != 3)
+ {
+ DBG (1, "sanei_scsi_open: device name `%s' is not valid: %s\n",
+ dev, strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+
+ if (cam_fd < 0)
+ {
+ cam_fd = open ("/dev/cam", O_RDWR);
+ if (cam_fd < 0)
+ {
+ DBG (1, "sanei_scsi_open: open(/dev/cam) failed: %s\n",
+ strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+ }
+ cam_scan.ucs_bus = bus;
+ cam_scan.ucs_target = target;
+ cam_scan.ucs_lun = lun;
+ if (ioctl (cam_fd, UAGT_CAM_SINGLE_SCAN, &cam_scan) < 0)
+ {
+ DBG (1, "sanei_scsi_open: ioctl(UAGT_CAM_SINGLE_SCAN) failed: %s\n",
+ strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+
+ for (fd = 0; fd < num_alloced; ++fd)
+ if (!fd_info[fd].in_use)
+ break;
+ fake_fd = 1;
+ }
+#elif USE == DOMAINOS_INTERFACE
+ {
+ static int index;
+ static status_$t status;
+ static unsigned long length_mapped;
+
+ DBG (1, "sanei_scsi_open: (dev='%s', int * fdp=%p, "
+ "SANEI_SCSI_Sense_Handler handler=%p)\n", dev, fdp, handler);
+
+ /* See if the server process has started yet */
+ if (!ServerInitialized)
+ {
+ static char *CommonAreaPath;
+
+ /* Initialize the server */
+ DBG (2, "Initializing Domain Server\n");
+
+ /* Map the area */
+ CommonAreaPath = tmpnam (NULL);
+ DBG (2, "Domain Server Common area name is '%s'\n", CommonAreaPath);
+ com = ms_$crmapl (CommonAreaPath, strlen (CommonAreaPath), 0,
+ sizeof (struct DomainServerCommon), ms_$cowriters,
+ &status);
+ DomainErrorCheck (status, "Can't open common area");
+ DBG (2, "Domain Server common area mapped\n");
+
+ /* Initialize the eventcounts */
+ ec2_$init (&com->CommandAvailable);
+ ec2_$init (&com->CommandAccepted);
+ ec2_$init (&com->ResultReady);
+ ec2_$init (&com->ResultAccepted);
+ DBG (2, "Domain Server EC's initialized\n");
+ /* Initialize the mutex locks */
+ mutex_$init (&com->CommandLock);
+ mutex_$init (&com->ResultLock);
+ DBG (2, "Domain Server MutexLock's initialized\n");
+
+ /* Initialize pointers to ECs */
+ CommandAcceptedPtr[0] = &com->CommandAccepted;
+ ResultReadyPtr[0] = &com->ResultReady;
+ time_$get_ec (time_$clockh_key, &CommandAcceptedPtr[1], &status);
+ DomainErrorCheck (status, "Can't get time EC");
+ ResultReadyPtr[1] = CommandAcceptedPtr[1];
+
+ /* Read the ResultReady EC value, to avoid race with the server */
+ ResultTriggerValue[0] = ec2_$read (com->ResultReady) + 1;
+
+ /* Now invoke the server */
+ ServerPID = fork ();
+ if (!ServerPID)
+ {
+ /* I am the child, call the initialization routine */
+ sanei_DomainOS_init (CommonAreaPath);
+ /* We get here when the server is done, so we just exit. */
+ exit (EXIT_SUCCESS);
+ }
+
+ /* The communication area is open, wait for the initial response */
+ ResultTriggerValue[1] = (ec2_$read (*ResultReadyPtr[1])
+ + DomainECWaitConstant);
+ index =
+ ec2_$wait_svc (ResultReadyPtr, ResultTriggerValue, 2, &status);
+ DomainErrorCheck (status, "Error waiting on initial open EC");
+ if (index != 1)
+ {
+ DBG (0, "Domain SANE Server never responded on startup\n");
+ /* Send a quit signal to the server */
+ kill (ServerPID, SIGQUIT);
+ return SANE_STATUS_INVAL;
+ }
+ /* Register a function to kill the server when we are done */
+ assert (!atexit (KillDomainServer));
+ ServerInitialized = 1;
+ }
+
+ /* Find fake fd. */
+ for (fd = 0; fd < num_alloced; ++fd)
+ if (!fd_info[fd].in_use)
+ break;
+ fake_fd = 1;
+
+ /* Send the command open to the server */
+ if (!mutex_$lock (&com->CommandLock, Wait16S))
+ {
+ DBG (0, "Could not obtain mutex lock for Open\n");
+ return SANE_STATUS_INVAL;
+ }
+ com->opcode = Open;
+ strcpy (com->open_path, dev);
+ CommandTriggerValue[0] = ec2_$read (com->CommandAccepted) + 1;
+ ec2_$advance (&com->CommandAvailable, &status);
+ DomainErrorCheck (status, "Can't advance CommandAvailable EC");
+ CommandTriggerValue[1] = (ec2_$read (*CommandAcceptedPtr[1])
+ + DomainECWaitConstant);
+ index = ec2_$wait_svc (CommandAcceptedPtr, CommandTriggerValue, 2,
+ &status);
+ DomainErrorCheck (status, "Error waiting on Open command acceptance EC");
+ if (index != 1)
+ {
+ DBG (0, "Domain SANE Server never accepted Open Command\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ /* Read the result */
+ status = com->CommandStatus;
+ DomainErrorCheck (status, "Opening device in server");
+
+ /* Now map the data area, and make it temporary */
+ DBG (2, "Mapping server's data block, name is '%s'\n", com->open_path);
+ pdata = ms_$mapl (com->open_path, strlen (com->open_path), 0,
+ DomainMaxDataSize + DomainSenseSize, ms_$cowriters,
+ ms_$wr, true, &length_mapped, &status);
+ DomainErrorCheck (status, "Mapping Server Data block");
+ assert (length_mapped >= DomainMaxDataSize + DomainSenseSize);
+ ms_$mk_temporary (pdata, &status);
+ DomainErrorCheck (status, "Can't make data block temporary");
+
+ /* Release the lock */
+ mutex_$unlock (&com->CommandLock);
+
+ if (status.all != status_$ok)
+ {
+ /* we have a failure, return an error code, and generate debug
+ output */
+ DBG (1, "sanei_scsi_open: acquire failed, Domain/OS status is %08x\n",
+ status.all);
+ error_$print (status);
+ return SANE_STATUS_INVAL;
+ }
+ else
+ {
+ /* device acquired, what else to do? */
+ fd = com->fd;
+ }
+ }
+#elif USE == FREEBSD_CAM_INTERFACE
+ if (1)
+ { /* 'if(1) {' makes my emacs c-mode indent better than
+ just '{' unfortunately, this only works if all of
+ the '{' are that way. */
+
+ struct cam_device *curdev;
+
+ fake_fd = 1;
+ fd = -1;
+
+ if ((curdev = cam_open_pass (dev, O_RDWR, NULL)) != NULL)
+ {
+ for (fd = 0; fd < CAM_MAXDEVS && cam_devices[fd] != NULL; fd++)
+ ;
+
+ if (fd == CAM_MAXDEVS)
+ {
+ DBG (1, "sanei_scsi_open: too many CAM devices (%d)\n", fd);
+ cam_close_device (curdev);
+ return SANE_STATUS_INVAL;
+ }
+ cam_devices[fd] = curdev;
+ }
+ else
+ {
+ DBG (1, "sanei_scsi_open: can't open device `%s': %s\n", dev,
+ strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+ }
+#elif USE == SCO_UW71_INTERFACE
+ {
+ pt_scsi_address_t dev_addr;
+ pt_handle_t pt_handle;
+ int bus, cnt, id, lun;
+
+ if (4 !=
+ sscanf (dev, "/dev/passthru0:%d,%d,%d,%d", &bus, &cnt, &id, &lun))
+ {
+ DBG (1, "sanei_scsi_open: device name `%s' is not valid: %s\n",
+ dev, strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+ dev_addr.psa_bus = bus;
+ dev_addr.psa_controller = cnt;
+ dev_addr.psa_target = id;
+ dev_addr.psa_lun = lun;
+
+ if (0 != pt_open (PASSTHRU_SCSI_ADDRESS, &dev_addr, PT_EXCLUSIVE,
+ &pt_handle))
+ {
+ DBG (1, "sanei_scsi_open: pt_open failed: %s!\n", strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+ else
+ fd = (int) pt_handle;
+ }
+#elif USE == MACOSX_INTERFACE
+ {
+# if defined (HAVE_IOKIT_SCSI_SCSICOMMANDOPERATIONCODES_H) || \
+ defined (HAVE_IOKIT_SCSI_COMMANDS_SCSICOMMANDOPERATIONCODES_H)
+ len = strlen (dev);
+ if (len > 2 && len % 2 == 0 && dev [0] == '<' && dev [len - 1] == '>')
+ {
+ len = (len - 2) / 2;
+ guid = (UInt8 *) malloc (len);
+ for (i = 0; i < len; i++)
+ {
+ if (sscanf (&dev [2 * i + 1], "%02x", &d) != 1)
+ break;
+ guid [i] = d;
+ }
+ if (i == len)
+ pdata = (void *) CFDataCreate (kCFAllocatorDefault, guid, len);
+ free (guid);
+ }
+# endif
+# ifdef HAVE_IOKIT_CDB_IOSCSILIB_H
+ if ((pdata == NULL) &&
+ (sscanf (dev, "u%ut%ul%u", &bus, &target, &lun) != 3))
+# else
+ if (pdata == NULL)
+# endif
+ {
+ DBG (1, "sanei_scsi_open: device name %s is not valid\n", dev);
+ return SANE_STATUS_INVAL;
+ }
+
+ /* Find fake fd. */
+ for (fd = 0; fd < num_alloced; ++fd)
+ if (!fd_info[fd].in_use)
+ break;
+ fake_fd = 1;
+ }
+#elif USE == WIN32_INTERFACE
+ {
+ char scsi_hca_name[20];
+ u_int hca = 0;
+
+ if (sscanf (dev, "h%ub%ut%ul%u", &hca, &bus, &target, &lun) != 4)
+ {
+ DBG (1, "sanei_scsi_open: device name %s is not valid\n", dev);
+ return SANE_STATUS_INVAL;
+ }
+
+ snprintf(scsi_hca_name, 19, "\\\\.\\Scsi%d:", hca);
+ scsi_hca_name[19] = 0;
+
+ fd = CreateFile(scsi_hca_name, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING,
+ FILE_FLAG_RANDOM_ACCESS, NULL );
+
+ if (fd == INVALID_HANDLE_VALUE) fd = -1;
+ }
+#else
+#if defined(SGIOCSTL) || (USE == SOLARIS_INTERFACE)
+ {
+ size_t len;
+
+ /* OpenStep and the Solaris SCG driver are a bit broken in that
+ the device name refers to a scsi _bus_, not an individual scsi
+ device. Hence, SANE has to fudge with the device name so we
+ know which target to connect to. For this purpose, we use the
+ last character in the device name as the target index. 'a' is
+ target 0, 'b', target 1, and so on... */
+
+ len = strlen (dev);
+ if (len <= 1)
+ {
+ DBG (1, "sanei_scsi_open: devicename `%s' too short\n", dev);
+ return SANE_STATUS_INVAL;
+ }
+
+ real_dev = strdup (dev);
+ real_dev[len - 1] = '\0';
+
+ target = dev[len - 1] - 'a';
+ if (target > 7)
+ {
+ DBG (1, "sanei_scsi_open: `%c' is not a valid target id\n",
+ dev[len - 1]);
+ return SANE_STATUS_INVAL;
+ }
+ dev = real_dev;
+ }
+#endif /* defined(SGIOCSTL) || (USE == SOLARIS_INTERFACE) */
+
+ fd = -1;
+#ifdef HAVE_RESMGR
+ fd = rsm_open_device(dev, O_RDWR | O_EXCL | O_NONBLOCK);
+#endif
+
+ if (fd == -1)
+ fd = open (dev, O_RDWR | O_EXCL
+#if USE == LINUX_INTERFACE
+ | O_NONBLOCK
+#endif
+ );
+ if (fd < 0)
+ {
+ SANE_Status status = SANE_STATUS_INVAL;
+
+ if (errno == EACCES)
+ status = SANE_STATUS_ACCESS_DENIED;
+ else if (errno == EBUSY)
+ status = SANE_STATUS_DEVICE_BUSY;
+
+ DBG (1, "sanei_scsi_open: open of `%s' failed: %s\n",
+ dev, strerror (errno));
+ return status;
+ }
+
+ if (real_dev)
+ free (real_dev);
+
+#ifdef SG_SET_TIMEOUT
+ /* Set large timeout since some scanners are slow but do not
+ disconnect... ;-( */
+ {
+ int timeout;
+ timeout = sane_scsicmd_timeout * sysconf(_SC_CLK_TCK);
+ ioctl (fd, SG_SET_TIMEOUT, &timeout);
+ }
+#endif
+
+#ifdef SGIOCSTL
+ {
+ struct scsi_adr sa;
+
+ sa.sa_target = target;
+ sa.sa_lun = 0;
+ if (ioctl (fd, SGIOCSTL, &sa) == -1)
+ {
+ DBG (1, "sanei_scsi_open: failed to attach to target: %u (%s)\n",
+ sa.sa_target, strerror (errno));
+ return SANE_STATUS_INVAL;
+ }
+ }
+#endif /* SGIOCSTL */
+#if USE == LINUX_INTERFACE
+ {
+ SG_scsi_id sid;
+ int ioctl_val;
+ int real_buffersize;
+ fdparms *fdpa = 0;
+ SG_scsi_id devinfo;
+
+ pdata = fdpa = malloc (sizeof (fdparms));
+ if (!pdata)
+ {
+ close (fd);
+ return SANE_STATUS_NO_MEM;
+ }
+ memset (fdpa, 0, sizeof (fdparms));
+ /* default: allow only one command to be sent to the SG driver
+ */
+ fdpa->sg_queue_max = 1;
+
+ /* Try to read the SG version. If the ioctl call is successful,
+ we have the new SG driver, and we can increase the buffer size
+ using another ioctl call.
+ If we have SG version 2.1.35 or above, we can additionally enable
+ command queueing.
+ */
+ if (0 == ioctl (fd, SG_GET_VERSION_NUM, &sg_version))
+ {
+ DBG (1, "sanei_scsi_open: SG driver version: %i\n", sg_version);
+
+ ioctl_val = ioctl (fd, SG_GET_SCSI_ID, &devinfo);
+ if (ioctl_val == EINVAL || ioctl_val == ENOTTY)
+ {
+ DBG (1, "sanei_scsi_open: The file %s is not an SG device file\n",
+ dev);
+ close (fd);
+ return SANE_STATUS_INVAL;
+ }
+
+ if (devinfo.scsi_type != 6 && devinfo.scsi_type != 3)
+ {
+ DBG (1,
+ "sanei_scsi_open: The device found for %s does not look like a scanner\n",
+ dev);
+ close (fd);
+ return SANE_STATUS_INVAL;
+ }
+
+ /* try to reserve a SG buffer of the size specified by *buffersize
+ */
+ ioctl (fd, SG_SET_RESERVED_SIZE, buffersize);
+
+ /* the set call may not be able to allocate as much memory
+ as requested, thus we read the actual buffer size.
+ */
+ if (0 == ioctl (fd, SG_GET_RESERVED_SIZE, &real_buffersize))
+ {
+ /* if we got more memory than requested, we stick with
+ with the requested value, in order to allow
+ sanei_scsi_open to check the buffer size exactly.
+ */
+ if (real_buffersize < *buffersize)
+ *buffersize = real_buffersize;
+ fdpa->buffersize = *buffersize;
+ }
+ else
+ {
+ DBG (1, "sanei_scsi_open: cannot read SG buffer size - %s\n",
+ strerror (errno));
+ close (fd);
+ return SANE_STATUS_NO_MEM;
+ }
+ DBG (1, "sanei_scsi_open_extended: using %i bytes as SCSI buffer\n",
+ *buffersize);
+
+ if (sg_version >= 20135)
+ {
+ DBG (1, "trying to enable low level command queueing\n");
+
+ if (0 == ioctl (fd, SG_GET_SCSI_ID, &sid))
+ {
+ DBG (1, "sanei_scsi_open: Host adapter queue depth: %i\n",
+ sid.d_queue_depth);
+
+ ioctl_val = 1;
+ if (0 == ioctl (fd, SG_SET_COMMAND_Q, &ioctl_val))
+ {
+ fdpa->sg_queue_max = sid.d_queue_depth;
+ if (fdpa->sg_queue_max <= 0)
+ fdpa->sg_queue_max = 1;
+ }
+ }
+ }
+ }
+ else
+ {
+ /* we have a really old SG driver version, or we're not opening
+ an SG device file
+ */
+ if (ioctl (fd, SG_GET_TIMEOUT, &ioctl_val) < 0)
+ {
+ DBG (1, "sanei_scsi_open: The file %s is not an SG device file\n",
+ dev);
+ close (fd);
+ return SANE_STATUS_INVAL;
+ }
+ if (sanei_scsi_max_request_size < *buffersize)
+ *buffersize = sanei_scsi_max_request_size;
+ fdpa->buffersize = *buffersize;
+ }
+ if (sg_version == 0)
+ {
+ DBG (1, "sanei_scsi_open: using old SG driver logic\n");
+ }
+ else
+ {
+ DBG (1,
+ "sanei_scsi_open: SG driver can change buffer size at run time\n");
+ if (fdpa->sg_queue_max > 1)
+ DBG (1, "sanei_scsi_open: low level command queueing enabled\n");
+#ifdef SG_IO
+ if (sg_version >= 30000)
+ {
+ DBG (1, "sanei_scsi_open: using new SG header structure\n");
+ }
+#endif
+ }
+ }
+#endif /* LINUX_INTERFACE */
+#endif /* !DECUNIX_INTERFACE */
+
+/* Note: this really relies on fd to start small. Windows starts a little higher than 3. */
+
+ if (fd >= num_alloced)
+ {
+ size_t new_size, old_size;
+
+ old_size = num_alloced * sizeof (fd_info[0]);
+ num_alloced = fd + 8;
+ new_size = num_alloced * sizeof (fd_info[0]);
+ if (fd_info)
+ fd_info = realloc (fd_info, new_size);
+ else
+ fd_info = malloc (new_size);
+ memset ((char *) fd_info + old_size, 0, new_size - old_size);
+ if (!fd_info)
+ {
+ if (!fake_fd)
+ close (fd);
+ return SANE_STATUS_NO_MEM;
+ }
+ }
+ fd_info[fd].in_use = 1;
+ fd_info[fd].sense_handler = handler;
+ fd_info[fd].sense_handler_arg = handler_arg;
+ fd_info[fd].fake_fd = fake_fd;
+ fd_info[fd].bus = bus;
+ fd_info[fd].target = target;
+ fd_info[fd].lun = lun;
+ fd_info[fd].pdata = pdata;
+
+#if USE == SOLARIS_INTERFACE || USE == SOLARIS_USCSI_INTERFACE
+ /* verify that the device really exists: */
+ if (!unit_ready (fd))
+ {
+ sanei_scsi_close (fd);
+ return SANE_STATUS_INVAL;
+ }
+#endif
+#if USE == SYSVR4_INTERFACE
+ memset (lastrcmd, 0, 16); /* reinitialize last read command block */
+#endif
+
+ if (fdp)
+ *fdp = fd;
+
+ return SANE_STATUS_GOOD;
+}
+
+#if USE == LINUX_INTERFACE
+/* The "wrapper" for the old open call */
+SANE_Status
+sanei_scsi_open (const char *dev, int *fdp,
+ SANEI_SCSI_Sense_Handler handler, void *handler_arg)
+{
+ int i = 0;
+ int wanted_buffersize = SCSIBUFFERSIZE, real_buffersize;
+ SANE_Status res;
+ char *cc, *cc1;
+ static int first_time = 1;
+
+ if (first_time)
+ {
+ cc = getenv ("SANE_SG_BUFFERSIZE");
+ if (cc)
+ {
+ i = strtol (cc, &cc1, 10);
+ if (cc != cc1 && i >= 32768)
+ wanted_buffersize = i;
+ }
+ }
+ else
+ wanted_buffersize = sanei_scsi_max_request_size;
+
+ real_buffersize = wanted_buffersize;
+ res = sanei_scsi_open_extended (dev, fdp, handler, handler_arg,
+ &real_buffersize);
+
+ /* make sure that we got as much memory as we wanted, otherwise
+ the backend might be confused
+ */
+ if (!first_time && real_buffersize != wanted_buffersize)
+ {
+ DBG (1, "sanei_scsi_open: could not allocate SG buffer memory "
+ "wanted: %i got: %i\n", wanted_buffersize, real_buffersize);
+ sanei_scsi_close (*fdp);
+ return SANE_STATUS_NO_MEM;
+ }
+
+ first_time = 0;
+ return res;
+}
+#else
+/* dummy for the proposed new open call */
+SANE_Status
+sanei_scsi_open_extended (const char *dev, int *fdp,
+ SANEI_SCSI_Sense_Handler handler,
+ void *handler_arg, int *buffersize)
+{
+ SANE_Status res;
+ res = sanei_scsi_open (dev, fdp, handler, handler_arg);
+ if (sanei_scsi_max_request_size < *buffersize)
+ *buffersize = sanei_scsi_max_request_size;
+ return res;
+}
+#endif
+
+void
+sanei_scsi_close (int fd)
+{
+#if USE == LINUX_INTERFACE
+ if (fd_info[fd].pdata)
+ {
+ req *req, *next_req;
+
+ /* make sure that there are no pending SCSI calls */
+ sanei_scsi_req_flush_all_extended (fd);
+
+ req = ((fdparms *) fd_info[fd].pdata)->sane_free_list;
+ while (req)
+ {
+ next_req = req->next;
+ free (req);
+ req = next_req;
+ }
+ free (fd_info[fd].pdata);
+ }
+#endif
+
+ fd_info[fd].in_use = 0;
+ fd_info[fd].sense_handler = 0;
+ fd_info[fd].sense_handler_arg = 0;
+
+#ifdef WIN32
+ CloseHandle(fd);
+#else
+ if (!fd_info[fd].fake_fd)
+ close (fd);
+#endif
+
+#if USE == FREEBSD_CAM_INTERFACE
+ cam_close_device (cam_devices[fd]);
+ cam_devices[fd] = NULL;
+#elif USE == DOMAINOS_INTERFACE
+ {
+ static int index;
+ static status_$t status;
+
+ DBG (1, "sanei_scsi_close: fd=%d\n", fd);
+
+ /* Send the command to the server */
+ if (!mutex_$lock (&com->CommandLock, Wait16S))
+ {
+ DBG (0, "Could not obtain mutex lock for Close command\n");
+ }
+ else
+ {
+ com->opcode = Close;
+ com->fd = fd;
+ CommandTriggerValue[0] = ec2_$read (com->CommandAccepted) + 1;
+ ec2_$advance (&com->CommandAvailable, &status);
+ DomainErrorCheck (status, "Can't advance CommandAvailable EC");
+ CommandTriggerValue[1] = (ec2_$read (*CommandAcceptedPtr[1])
+ + DomainECWaitConstant);
+ index = ec2_$wait_svc (CommandAcceptedPtr, CommandTriggerValue, 2,
+ &status);
+ DomainErrorCheck (status,
+ "Error waiting on Close command acceptance EC");
+ if (index != 1)
+ {
+ DBG (0, "Domain SANE Server never accepted Close Command\n");
+ }
+
+ /* Read the result */
+ status = com->CommandStatus;
+ /* Release the lock */
+ mutex_$unlock (&com->CommandLock);
+ }
+
+ /* Unmap the data area */
+ ms_$unmap (fd_info[com->fd].pdata, DomainMaxDataSize + DomainSenseSize,
+ &status);
+ DomainErrorCheck (status, "Error unmapping device data area");
+ }
+#endif /* USE == DOMAINOS_INTERFACE */
+
+#if USE == OS2_INTERFACE
+ close_aspi ();
+#endif /* USE == OS2_INTERFACE */
+
+#if USE == MACOSX_INTERFACE
+ if (fd_info[fd].pdata)
+ CFRelease (fd_info[fd].pdata);
+#endif /* USE == MACOSX_INTERFACE */
+}
+
+
+#if USE == DOMAINOS_INTERFACE
+# define WE_HAVE_ASYNC_SCSI
+
+void
+sanei_scsi_req_flush_all (void)
+{
+ status_$t status;
+
+ DBG (1, "sanei_scsi_req_flush_all: ()\n");
+ /* I have never seen this called, and I'm not sure what to do with it,
+ so I guarantee that it will generate a fault, and I can add support
+ for it. */
+ assert (1 == 0);
+}
+
+
+SANE_Status
+sanei_scsi_req_enter2 (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size, void **idp)
+{
+ SANEI_SCSI_Sense_Handler handler;
+ static int index;
+ static SANE_Status sane_status;
+ static status_$t status;
+ static scsi_$status_t SCSIStatus;
+ static void *buf_ptr;
+
+ if (dst_size)
+ DBG (1, "sanei_scsi_req_enter2: (fd=%x, cmd=%p, cmd_size=%x, "
+ "src=%p, src_size=%x, dst=%p, dst_size=%x, *idp=%p)\n",
+ fd, cmd, cmd_size, src, src_size, dst, *dst_size, idp);
+ else
+ DBG (1, "sanei_scsi_req_enter2: (fd=%x, cmd=%p, cmd_size=%x, "
+ "src=%p, src_size=%x, dst=%p, dst_size=NULL, *idp=%p)\n",
+ fd, src, src_size, dst, idp);
+
+ /* Lock the command structure */
+ if (!mutex_$lock (&com->CommandLock, mutex_$wait_forever))
+ {
+ DBG (0, "Could not obtain mutex lock for Enter Command\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ /* Fill in the command structure */
+ com->opcode = Enter;
+ com->fd = fd;
+ com->cdb_size = cmd_size;
+ if (dst_size)
+ com->dst_size = *dst_size;
+ memcpy (&com->cdb, cmd, com->cdb_size);
+
+ /* figure out if this is a read or a write */
+ if (dst_size && *dst_size)
+ {
+ /* dest buffer specified, must be a read */
+ /* assert (com->cdb_size == src_size); */
+ com->direction = scsi_read;
+ buf_ptr = dst;
+ com->buf_size = *dst_size;
+ }
+ else
+ {
+ /* no dest buffer, must be a write */
+ /* assert (com->cdb_size <= src_size); */
+ com->direction = scsi_write;
+ buf_ptr = (char *) src;
+ com->buf_size = src_size;
+ if (com->buf_size)
+ memcpy (fd_info[fd].pdata, buf_ptr, com->buf_size);
+ }
+
+ CommandTriggerValue[0] = ec2_$read (com->CommandAccepted) + 1;
+ ec2_$advance (&com->CommandAvailable, &status);
+ DomainErrorCheck (status, "Can't advance CommandAvailable EC");
+ CommandTriggerValue[1] = (ec2_$read (*CommandAcceptedPtr[1])
+ + DomainECWaitConstant);
+ index = ec2_$wait_svc (CommandAcceptedPtr, CommandTriggerValue, 2, &status);
+ DomainErrorCheck (status, "Error waiting on Enter command acceptance EC");
+ if (index != 1)
+ {
+ DBG (0, "Domain SANE Server never accepted Enter Command\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ /* Read the result */
+ status = com->CommandStatus;
+ SCSIStatus = com->SCSIStatus;
+
+ /* Release the lock */
+ mutex_$unlock (&com->CommandLock);
+
+ /* Now decode the return status */
+ if (status.all)
+ DBG (1, "Server returned status %08x from Enter command\n", status.all);
+ switch (status.all)
+ {
+ case status_$ok:
+ sane_status = SANE_STATUS_GOOD;
+ break;
+ case scsi_$dma_underrun:
+ sane_status = SANE_STATUS_IO_ERROR;
+ /* This error is generated by the HP and UMAX backends. They
+ ask for too much data. For now, the error is ignored :-( */
+ sane_status = SANE_STATUS_GOOD;
+ break;
+ case scsi_$operation_timeout:
+ sane_status = SANE_STATUS_DEVICE_BUSY;
+ break;
+ case scsi_$hdwr_failure: /* received when both scanners were active */
+ sane_status = SANE_STATUS_IO_ERROR;
+ break;
+ case (status_$ok | 0x80000000):
+ /* Special - no Domain/OS error, but fail bit set means to check
+ SCSI operation status. */
+ DBG (1, "Server returned SCSI status of %08x\n", SCSIStatus);
+ switch (SCSIStatus)
+ {
+ case scsi_check_condition:
+ /* Call the sense handler, if defined */
+ handler = fd_info[com->fd].sense_handler;
+ if (handler)
+ (*handler) (fd, ((u_char *) fd_info[fd].pdata
+ + DomainMaxDataSize),
+ fd_info[com->fd].sense_handler_arg);
+ sane_status = SANE_STATUS_IO_ERROR;
+ break;
+ case scsi_busy:
+ sane_status = SANE_STATUS_DEVICE_BUSY;
+ break;
+ default:
+ DBG (0, "Error - Unrecognized SCSI status %08x returned from "
+ "Enter command\n", SCSIStatus);
+ sane_status = SANE_STATUS_IO_ERROR;
+ exit (EXIT_FAILURE);
+ }
+ break;
+ default:
+ DBG (0, "Unmapped status (%08x) returned from Domain SANE Server\n",
+ status.all);
+ sane_status = SANE_STATUS_IO_ERROR;
+ }
+
+ /* If a read, copy the data into the destination buffer */
+ if ((com->direction == scsi_read) && com->dst_size)
+ memcpy (buf_ptr, fd_info[fd].pdata, com->dst_size);
+
+ return sane_status;
+}
+
+
+SANE_Status
+sanei_scsi_req_wait (void *id)
+{
+ SANE_Status status;
+ DBG (1, "sanei_scsi_req_wait: (id=%p)\n", id);
+ status = SANE_STATUS_GOOD;
+ return status;
+}
+
+
+SANE_Status
+sanei_scsi_cmd2 (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+{
+ SANE_Status status;
+ void *id;
+
+ DBG (1, "sanei_scsi_cmd2: (fd=%d)\n", fd);
+ status =
+ sanei_scsi_req_enter2 (fd, cmd, cmd_size, src, src_size, dst, dst_size,
+ &id);
+ if (status != SANE_STATUS_GOOD)
+ return status;
+ return sanei_scsi_req_wait (id);
+}
+
+#endif /* USE == DOMAINOS_INTERFACE */
+
+
+#if USE == LINUX_INTERFACE
+
+#include <ctype.h>
+#include <signal.h>
+
+#include <sys/time.h>
+
+#define WE_HAVE_ASYNC_SCSI
+#define WE_HAVE_FIND_DEVICES
+
+static int pack_id = 0;
+static int need_init = 1;
+static sigset_t all_signals;
+
+#define ATOMIC(s) \
+do \
+ { \
+ sigset_t old_mask; \
+ \
+ if (need_init) \
+ { \
+ need_init = 0; \
+ sigfillset (&all_signals); \
+ } \
+ sigprocmask (SIG_BLOCK, &all_signals, &old_mask); \
+ {s;} \
+ sigprocmask (SIG_SETMASK, &old_mask, 0); \
+ } \
+while (0)
+
+static void
+issue (struct req *req)
+{
+ ssize_t nwritten;
+ fdparms *fdp;
+ struct req *rp;
+ int retries;
+ int ret;
+
+ if (!req)
+ return;
+
+ fdp = (fdparms *) fd_info[req->fd].pdata;
+ DBG (4, "sanei_scsi.issue: %p\n", (void *) req);
+
+ rp = fdp->sane_qhead;
+ while (rp && rp->running)
+ rp = rp->next;
+
+ while (rp && fdp->sg_queue_used < fdp->sg_queue_max)
+ {
+ retries = 20;
+ while (retries)
+ {
+ errno = 0;
+#ifdef SG_IO
+ if (sg_version < 30000)
+ {
+#endif
+ ATOMIC (rp->running = 1;
+ nwritten = write (rp->fd, &rp->sgdata.cdb,
+ rp->sgdata.cdb.hdr.pack_len);
+ ret = 0;
+ if (nwritten != rp->sgdata.cdb.hdr.pack_len)
+ {
+ /* ENOMEM can easily happen, if both command queueing
+ inside the SG driver and large buffers are used.
+ Therefore, if ENOMEM does not occur for the first
+ command in the queue, we simply try to issue
+ it later again.
+ */
+ if (errno == EAGAIN
+ || (errno == ENOMEM && rp != fdp->sane_qhead))
+ {
+ /* don't try to send the data again, but
+ wait for the next call to issue()
+ */
+ rp->running = 0;}
+ }
+ );
+#ifdef SG_IO
+ }
+ else
+ {
+ ATOMIC (rp->running = 1;
+ ret = ioctl(rp->fd, SG_IO, &rp->sgdata.sg3.hdr);
+ nwritten = 0;
+ if (ret < 0)
+ {
+ /* ENOMEM can easily happen, if both command queuein
+ inside the SG driver and large buffers are used.
+ Therefore, if ENOMEM does not occur for the first
+ command in the queue, we simply try to issue
+ it later again.
+ */
+ if (errno == EAGAIN
+ || (errno == ENOMEM && rp != fdp->sane_qhead))
+ {
+ /* don't try to send the data again, but
+ wait for the next call to issue()
+ */
+ rp->running = 0;
+ }
+ else /* game over */
+ {
+ rp->running = 0;
+ rp->done = 1;
+ rp->status = SANE_STATUS_IO_ERROR;
+ }
+ }
+ );
+ IF_DBG (if (DBG_LEVEL >= 255)
+ system ("cat /proc/scsi/sg/debug 1>&2");)
+ }
+#endif
+ if (rp == fdp->sane_qhead && errno == EAGAIN)
+ {
+ retries--;
+ usleep (10000);
+ }
+ else
+ retries = 0;
+ }
+
+#ifndef SG_IO
+ if (nwritten != rp->sgdata.cdb.hdr.pack_len)
+#else
+ if ((sg_version < 30000 && nwritten != rp->sgdata.cdb.hdr.pack_len)
+ || (sg_version >= 30000 && ret < 0))
+#endif
+ {
+ if (rp->running)
+ {
+#ifdef SG_IO
+ if (sg_version < 30000)
+#endif
+ DBG (1, "sanei_scsi.issue: bad write (errno=%i) %s %li\n",
+ errno, strerror (errno), (long)nwritten);
+#ifdef SG_IO
+ else if (sg_version > 30000)
+ DBG (1, "sanei_scsi.issue: SG_IO ioctl error (errno=%i, ret=%d) %s\n",
+ errno, ret, strerror (errno));
+#endif
+ rp->done = 1;
+ if (errno == ENOMEM)
+ {
+ DBG (1, "sanei_scsi.issue: SG_BIG_BUF inconsistency? "
+ "Check file PROBLEMS.\n");
+ rp->status = SANE_STATUS_NO_MEM;
+ }
+ else
+ rp->status = SANE_STATUS_IO_ERROR;
+ }
+ else
+ {
+ if (errno == ENOMEM)
+ DBG (1, "issue: ENOMEM - cannot queue SCSI command. "
+ "Trying again later.\n");
+ else
+ DBG (1, "issue: EAGAIN - cannot queue SCSI command. "
+ "Trying again later.\n");
+ }
+ break; /* in case of an error don't try to queue more commands */
+ }
+ else
+ {
+#ifdef SG_IO
+ if (sg_version < 30000)
+#endif
+ req->status = SANE_STATUS_IO_ERROR;
+#ifdef SG_IO
+ else if (sg_version > 30000) /* SG_IO is synchronous, we're all set */
+ req->status = SANE_STATUS_GOOD;
+#endif
+ }
+ fdp->sg_queue_used++;
+ rp = rp->next;
+ }
+}
+
+ void sanei_scsi_req_flush_all_extended (int fd)
+ {
+ fdparms *fdp;
+ struct req *req, *next_req;
+ int len, count;
+
+ fdp = (fdparms *) fd_info[fd].pdata;
+ for (req = fdp->sane_qhead; req; req = next_req)
+ {
+ if (req->running && !req->done)
+ {
+ count = sane_scsicmd_timeout * 10;
+ while (count)
+ {
+ errno = 0;
+#ifdef SG_IO
+ if (sg_version < 30000)
+#endif
+ len =
+ read (fd, &req->sgdata.cdb,
+ req->sgdata.cdb.hdr.reply_len);
+#ifdef SG_IO
+ else
+ len = read (fd, &req->sgdata.sg3.hdr, sizeof (Sg_io_hdr));
+#endif
+ if (len >= 0 || (len < 0 && errno != EAGAIN))
+ break;
+ usleep (100000);
+ count--;
+ }
+ ((fdparms *) fd_info[req->fd].pdata)->sg_queue_used--;
+ }
+ next_req = req->next;
+
+ req->next = fdp->sane_free_list;
+ fdp->sane_free_list = req;
+ }
+
+ fdp->sane_qhead = fdp->sane_qtail = 0;
+ }
+
+ void sanei_scsi_req_flush_all ()
+ {
+ int fd, i, j = 0;
+
+ /* sanei_scsi_open allows only one open file handle, so we
+ can simply look for the first entry where in_use is set
+ */
+
+ fd = num_alloced;
+ for (i = 0; i < num_alloced; i++)
+ if (fd_info[i].in_use)
+ {
+ j++;
+ fd = i;
+ }
+
+ assert (j < 2);
+
+ if (fd < num_alloced)
+ sanei_scsi_req_flush_all_extended (fd);
+ }
+
+ SANE_Status
+ sanei_scsi_req_enter2 (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size, void **idp)
+ {
+ struct req *req;
+ size_t size;
+ fdparms *fdp;
+
+ fdp = (fdparms *) fd_info[fd].pdata;
+
+ if (fdp->sane_free_list)
+ {
+ req = fdp->sane_free_list;
+ fdp->sane_free_list = req->next;
+ req->next = 0;
+ }
+ else
+ {
+#ifdef SG_IO
+ if (sg_version < 30000)
+#endif
+ size = (sizeof (*req) - sizeof (req->sgdata.cdb.data)
+ + fdp->buffersize);
+#ifdef SG_IO
+ else
+ size = sizeof (*req) + MAX_CDB + fdp->buffersize
+ - sizeof (req->sgdata.sg3.data);
+#endif
+ req = malloc (size);
+ if (!req)
+ {
+ DBG (1, "sanei_scsi_req_enter: failed to malloc %lu bytes\n",
+ (u_long) size);
+ return SANE_STATUS_NO_MEM;
+ }
+ }
+ req->fd = fd;
+ req->running = 0;
+ req->done = 0;
+ req->status = SANE_STATUS_GOOD;
+ req->dst = dst;
+ req->dst_len = dst_size;
+#ifdef SG_IO
+ if (sg_version < 30000)
+ {
+#endif
+ memset (&req->sgdata.cdb.hdr, 0, sizeof (req->sgdata.cdb.hdr));
+ req->sgdata.cdb.hdr.pack_id = pack_id++;
+ req->sgdata.cdb.hdr.pack_len = cmd_size + src_size
+ + sizeof (req->sgdata.cdb.hdr);
+ req->sgdata.cdb.hdr.reply_len = (dst_size ? *dst_size : 0)
+ + sizeof (req->sgdata.cdb.hdr);
+ memcpy (&req->sgdata.cdb.data, cmd, cmd_size);
+ memcpy (&req->sgdata.cdb.data[cmd_size], src, src_size);
+ if (CDB_SIZE (*(const u_char *) cmd) != cmd_size)
+ {
+ if (ioctl (fd, SG_NEXT_CMD_LEN, &cmd_size))
+ {
+ DBG (1,
+ "sanei_scsi_req_enter2: ioctl to set command length failed\n");
+ }
+ }
+#ifdef SG_IO
+ }
+ else
+ {
+ memset (&req->sgdata.sg3.hdr, 0, sizeof (req->sgdata.sg3.hdr));
+ req->sgdata.sg3.hdr.interface_id = 'S';
+ req->sgdata.sg3.hdr.cmd_len = cmd_size;
+ req->sgdata.sg3.hdr.iovec_count = 0;
+ req->sgdata.sg3.hdr.mx_sb_len = SENSE_MAX;
+ /* read or write? */
+ if (dst_size && *dst_size)
+ {
+ req->sgdata.sg3.hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ req->sgdata.sg3.hdr.dxfer_len = *dst_size;
+ req->sgdata.sg3.hdr.dxferp = dst;
+ }
+ else if (src_size)
+ {
+ req->sgdata.sg3.hdr.dxfer_direction = SG_DXFER_TO_DEV;
+ if (src_size > fdp->buffersize)
+ {
+ DBG (1,
+ "sanei_scsi_req_enter2 warning: truncating write data "
+ "from requested %li bytes to allowed %li bytes\n",
+ (long)src_size, (long)fdp->buffersize);
+ src_size = fdp->buffersize;
+ }
+ req->sgdata.sg3.hdr.dxfer_len = src_size;
+ memcpy (&req->sgdata.sg3.data[MAX_CDB], src, src_size);
+ req->sgdata.sg3.hdr.dxferp = &req->sgdata.sg3.data[MAX_CDB];
+ }
+ else
+ {
+ req->sgdata.sg3.hdr.dxfer_direction = SG_DXFER_NONE;
+ }
+ if (cmd_size > MAX_CDB)
+ {
+ DBG (1, "sanei_scsi_req_enter2 warning: truncating write data "
+ "from requested %li bytes to allowed %i bytes\n",
+ (long)cmd_size, MAX_CDB);
+ cmd_size = MAX_CDB;
+ }
+ memcpy (req->sgdata.sg3.data, cmd, cmd_size);
+ req->sgdata.sg3.hdr.cmdp = req->sgdata.sg3.data;
+ req->sgdata.sg3.hdr.sbp = &(req->sgdata.sg3.sense_buffer[0]);
+ req->sgdata.sg3.hdr.timeout = 1000 * sane_scsicmd_timeout;
+#ifdef ENABLE_SCSI_DIRECTIO
+ /* for the adventurous: If direct IO is used,
+ the kernel locks the buffer. This can lead to conflicts,
+ if a backend uses shared memory.
+ OTOH, direct IO may be faster, and it reduces memory usage
+ */
+ req->sgdata.sg3.hdr.flags = SG_FLAG_DIRECT_IO;
+#else
+ req->sgdata.sg3.hdr.flags = 0;
+#endif
+ req->sgdata.sg3.hdr.pack_id = pack_id++;
+ req->sgdata.sg3.hdr.usr_ptr = 0;
+ }
+#endif
+
+ req->next = 0;
+ ATOMIC (if (fdp->sane_qtail)
+ {
+ fdp->sane_qtail->next = req; fdp->sane_qtail = req;}
+ else
+ fdp->sane_qhead = fdp->sane_qtail = req);
+
+ DBG (4, "scsi_req_enter: entered %p\n", (void *) req);
+
+ *idp = req;
+ issue (req);
+
+ DBG (10, "scsi_req_enter: queue_used: %i, queue_max: %i\n",
+ ((fdparms *) fd_info[fd].pdata)->sg_queue_used,
+ ((fdparms *) fd_info[fd].pdata)->sg_queue_max);
+
+ return SANE_STATUS_GOOD;
+ }
+
+ SANE_Status sanei_scsi_req_wait (void *id)
+ {
+ SANE_Status status = SANE_STATUS_GOOD;
+ struct req *req = id;
+ ssize_t nread = 0;
+
+ /* we don't support out-of-order completion */
+ assert (req == ((fdparms *) fd_info[req->fd].pdata)->sane_qhead);
+
+ DBG (4, "sanei_scsi_req_wait: waiting for %p\n", (void *) req);
+
+ issue (req); /* ensure the command is running */
+ if (req->done)
+ {
+ issue (req->next); /* issue next command, if any */
+ status = req->status;
+ }
+ else
+ {
+#ifdef SG_IO
+ if (sg_version < 30000)
+ {
+#endif
+ fd_set readable;
+
+ /* wait for command completion: */
+ FD_ZERO (&readable);
+ FD_SET (req->fd, &readable);
+ select (req->fd + 1, &readable, 0, 0, 0);
+
+ /* now atomically read result and set DONE: */
+ ATOMIC (nread = read (req->fd, &req->sgdata.cdb,
+ req->sgdata.cdb.hdr.reply_len);
+ req->done = 1);
+#ifdef SG_IO
+ }
+ else
+ {
+ IF_DBG (if (DBG_LEVEL >= 255)
+ system ("cat /proc/scsi/sg/debug 1>&2");)
+
+ /* set DONE: */
+ nread = 0; /* unused in this code path */
+ req->done = 1;
+ }
+#endif
+
+ if (fd_info[req->fd].pdata)
+ ((fdparms *) fd_info[req->fd].pdata)->sg_queue_used--;
+
+ /* Now issue next command asap, if any. We can't do this
+ earlier since the Linux kernel has space for just one big
+ buffer. */
+ issue (req->next);
+
+ DBG (4, "sanei_scsi_req_wait: read %ld bytes\n", (long) nread);
+
+ if (nread < 0)
+ {
+ DBG (1, "sanei_scsi_req_wait: read returned %ld (errno=%d)\n",
+ (long) nread, errno);
+ status = SANE_STATUS_IO_ERROR;
+ }
+ else
+ {
+#ifdef SG_IO
+ if (sg_version < 30000)
+ {
+#endif
+ nread -= sizeof (req->sgdata.cdb.hdr);
+
+ /* check for errors, but let the sense_handler decide.... */
+ if ((req->sgdata.cdb.hdr.result != 0) ||
+ (((req->sgdata.cdb.hdr.sense_buffer[0] & 0x7f) != 0)
+#ifdef HAVE_SG_TARGET_STATUS
+ /* this is messy... Sometimes it happens that we have
+ a valid looking sense buffer, but the DRIVER_SENSE
+ bit is not set. Moreover, we can check this only for
+ not tooo old SG drivers
+ */
+ && (req->sgdata.cdb.hdr.driver_status & DRIVER_SENSE)
+#endif
+ ))
+ {
+ SANEI_SCSI_Sense_Handler handler
+ = fd_info[req->fd].sense_handler;
+ void *arg = fd_info[req->fd].sense_handler_arg;
+
+ DBG (1,
+ "sanei_scsi_req_wait: SCSI command complained: %s\n",
+ strerror (req->sgdata.cdb.hdr.result));
+ DBG (10,
+ "sense buffer: %02x %02x %02x %02x %02x %02x %02x %02x"
+ " %02x %02x %02x %02x %02x %02x %02x %02x\n",
+ req->sgdata.cdb.hdr.sense_buffer[0],
+ req->sgdata.cdb.hdr.sense_buffer[1],
+ req->sgdata.cdb.hdr.sense_buffer[2],
+ req->sgdata.cdb.hdr.sense_buffer[3],
+ req->sgdata.cdb.hdr.sense_buffer[4],
+ req->sgdata.cdb.hdr.sense_buffer[5],
+ req->sgdata.cdb.hdr.sense_buffer[6],
+ req->sgdata.cdb.hdr.sense_buffer[7],
+ req->sgdata.cdb.hdr.sense_buffer[8],
+ req->sgdata.cdb.hdr.sense_buffer[9],
+ req->sgdata.cdb.hdr.sense_buffer[10],
+ req->sgdata.cdb.hdr.sense_buffer[11],
+ req->sgdata.cdb.hdr.sense_buffer[12],
+ req->sgdata.cdb.hdr.sense_buffer[13],
+ req->sgdata.cdb.hdr.sense_buffer[14],
+ req->sgdata.cdb.hdr.sense_buffer[15]);
+#ifdef HAVE_SG_TARGET_STATUS
+ /* really old SG header do not define target_status,
+ host_status and driver_status
+ */
+ DBG (10, "target status: %02x host status: %02x"
+ " driver status: %02x\n",
+ req->sgdata.cdb.hdr.target_status,
+ req->sgdata.cdb.hdr.host_status,
+ req->sgdata.cdb.hdr.driver_status);
+
+ if (req->sgdata.cdb.hdr.host_status == DID_NO_CONNECT || req->sgdata.cdb.hdr.host_status == DID_BUS_BUSY || req->sgdata.cdb.hdr.host_status == DID_TIME_OUT || req->sgdata.cdb.hdr.driver_status == DRIVER_BUSY || req->sgdata.cdb.hdr.target_status == 0x04) /* BUSY */
+#else
+ if (req->sgdata.cdb.hdr.result == EBUSY)
+#endif
+ status = SANE_STATUS_DEVICE_BUSY;
+ else if (handler)
+ /* sense handler should return SANE_STATUS_GOOD if it
+ decided all was ok afterall */
+ status =
+ (*handler) (req->fd, req->sgdata.cdb.hdr.sense_buffer,
+ arg);
+ else
+ status = SANE_STATUS_IO_ERROR;
+ }
+
+ /* if we are ok so far, copy over the return data */
+ if (status == SANE_STATUS_GOOD)
+ {
+ if (req->dst)
+ memcpy (req->dst, req->sgdata.cdb.data, nread);
+
+ if (req->dst_len)
+ *req->dst_len = nread;
+ }
+#ifdef SG_IO
+ }
+ else
+ {
+ /* check for errors, but let the sense_handler decide.... */
+ if (((req->sgdata.sg3.hdr.info & SG_INFO_CHECK) != 0)
+ || ((req->sgdata.sg3.hdr.sb_len_wr > 0)
+ && ((req->sgdata.sg3.sense_buffer[0] & 0x7f) != 0)
+ && (req->sgdata.sg3.hdr.
+ driver_status & DRIVER_SENSE)))
+ {
+ SANEI_SCSI_Sense_Handler handler
+ = fd_info[req->fd].sense_handler;
+ void *arg = fd_info[req->fd].sense_handler_arg;
+
+ DBG (1,
+ "sanei_scsi_req_wait: SCSI command complained: %s\n",
+ strerror (errno));
+ DBG (10,
+ "sense buffer: %02x %02x %02x %02x %02x %02x %02x %02x"
+ " %02x %02x %02x %02x %02x %02x %02x %02x\n",
+ req->sgdata.sg3.sense_buffer[0],
+ req->sgdata.sg3.sense_buffer[1],
+ req->sgdata.sg3.sense_buffer[2],
+ req->sgdata.sg3.sense_buffer[3],
+ req->sgdata.sg3.sense_buffer[4],
+ req->sgdata.sg3.sense_buffer[5],
+ req->sgdata.sg3.sense_buffer[6],
+ req->sgdata.sg3.sense_buffer[7],
+ req->sgdata.sg3.sense_buffer[8],
+ req->sgdata.sg3.sense_buffer[9],
+ req->sgdata.sg3.sense_buffer[10],
+ req->sgdata.sg3.sense_buffer[11],
+ req->sgdata.sg3.sense_buffer[12],
+ req->sgdata.sg3.sense_buffer[13],
+ req->sgdata.sg3.sense_buffer[14],
+ req->sgdata.sg3.sense_buffer[15]);
+ DBG (10,
+ "target status: %02x host status: %04x"
+ " driver status: %04x\n", req->sgdata.sg3.hdr.status,
+ req->sgdata.sg3.hdr.host_status,
+ req->sgdata.sg3.hdr.driver_status);
+
+ /* the first three tests below are an replacement of the
+ error "classification" as it was with the old SG driver,
+ the fourth test is new.
+ */
+ if (req->sgdata.sg3.hdr.host_status == SG_ERR_DID_NO_CONNECT || req->sgdata.sg3.hdr.host_status == SG_ERR_DID_BUS_BUSY || req->sgdata.sg3.hdr.host_status == SG_ERR_DID_TIME_OUT || req->sgdata.sg3.hdr.driver_status == DRIVER_BUSY || req->sgdata.sg3.hdr.masked_status == 0x04) /* BUSY */
+ status = SANE_STATUS_DEVICE_BUSY;
+ else if (handler && req->sgdata.sg3.hdr.sb_len_wr)
+ /* sense handler should return SANE_STATUS_GOOD if it
+ decided all was ok afterall */
+ status =
+ (*handler) (req->fd, req->sgdata.sg3.sense_buffer,
+ arg);
+
+ /* status bits INTERMEDIATE and CONDITION MET should not
+ result in an error; neither should reserved bits
+ */
+ else if (((req->sgdata.sg3.hdr.status & 0x2a) == 0)
+ && (req->sgdata.sg3.hdr.host_status ==
+ SG_ERR_DID_OK)
+ &&
+ ((req->sgdata.sg3.hdr.
+ driver_status & ~SG_ERR_DRIVER_SENSE) ==
+ SG_ERR_DRIVER_OK))
+ status = SANE_STATUS_GOOD;
+ else
+ status = SANE_STATUS_IO_ERROR;
+ }
+
+#if 0
+ /* Sometimes the Linux SCSI system reports bogus resid values.
+ Observed with lk 2.4.5, 2.4.13, aic7xxx and sym53c8xx drivers,
+ if command queueing is used. So we better issue only a warning
+ */
+ if (status == SANE_STATUS_GOOD)
+ {
+ if (req->dst_len)
+ {
+ *req->dst_len -= req->sgdata.sg3.hdr.resid;
+ }
+ }
+#endif
+ if (req->sgdata.sg3.hdr.resid)
+ {
+ DBG (1,
+ "sanei_scsi_req_wait: SG driver returned resid %i\n",
+ req->sgdata.sg3.hdr.resid);
+ DBG (1,
+ " NOTE: This value may be bogus\n");
+ }
+ }
+#endif
+ }
+ }
+
+ /* dequeue and release processed request: */
+ ATOMIC (((fdparms *) fd_info[req->fd].pdata)->sane_qhead
+ = ((fdparms *) fd_info[req->fd].pdata)->sane_qhead->next;
+ if (!((fdparms *) fd_info[req->fd].pdata)->sane_qhead)
+ ((fdparms *) fd_info[req->fd].pdata)->sane_qtail = 0;
+ req->next = ((fdparms *) fd_info[req->fd].pdata)->sane_free_list;
+ ((fdparms *) fd_info[req->fd].pdata)->sane_free_list = req);
+ return status;
+ }
+
+ SANE_Status
+ sanei_scsi_cmd2 (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+ SANE_Status status;
+ void *id;
+
+ status =
+ sanei_scsi_req_enter2 (fd, cmd, cmd_size, src, src_size, dst, dst_size,
+ &id);
+ if (status != SANE_STATUS_GOOD)
+ return status;
+ return sanei_scsi_req_wait (id);
+ }
+
+/* The following code (up to and including sanei_scsi_find_devices() )
+ is trying to match device/manufacturer names and/or SCSI addressing
+ numbers (i.e. <host,bus,id,lun>) with a sg device file name
+ (e.g. /dev/sg3).
+*/
+#define PROCFILE "/proc/scsi/scsi"
+#define DEVFS_MSK "/dev/scsi/host%d/bus%d/target%d/lun%d/generic"
+#define SCAN_MISSES 5
+
+/* Some <scsi/scsi.h> headers don't have the following define */
+#ifndef SCSI_IOCTL_GET_IDLUN
+#define SCSI_IOCTL_GET_IDLUN 0x5382
+#endif
+
+ static int lx_sg_dev_base = -1;
+ static int lx_devfs = -1;
+
+ static const struct lx_device_name_list_tag
+ {
+ const char *prefix;
+ char base;
+ }
+ lx_dnl[] =
+ {
+ {
+ "/dev/sg", 0}
+ ,
+ {
+ "/dev/sg", 'a'}
+ ,
+ {
+ "/dev/uk", 0}
+ ,
+ {
+ "/dev/gsc", 0}
+ };
+
+ static int /* Returns open sg file descriptor, or -1 for no access,
+ or -2 for not found (or other error) */
+ lx_mk_devicename (int guess_devnum, char *name, size_t name_len)
+ {
+ int dev_fd, k, dnl_len;
+ const struct lx_device_name_list_tag *dnp;
+
+ dnl_len = NELEMS (lx_dnl);
+ k = ((-1 == lx_sg_dev_base) ? 0 : lx_sg_dev_base);
+ for (; k < dnl_len; ++k)
+ {
+ dnp = &lx_dnl[k];
+ if (dnp->base)
+ snprintf (name, name_len, "%s%c", dnp->prefix,
+ dnp->base + guess_devnum);
+ else
+ snprintf (name, name_len, "%s%d", dnp->prefix, guess_devnum);
+ dev_fd = -1;
+#ifdef HAVE_RESMGR
+ dev_fd = rsm_open_device (name, O_RDWR | O_NONBLOCK);
+#endif
+ if (dev_fd == -1)
+ dev_fd = open (name, O_RDWR | O_NONBLOCK);
+ if (dev_fd >= 0)
+ {
+ lx_sg_dev_base = k;
+ return dev_fd;
+ }
+ else if ((EACCES == errno) || (EBUSY == errno))
+ {
+ lx_sg_dev_base = k;
+ return -1;
+ }
+ if (-1 != lx_sg_dev_base)
+ return -2;
+ }
+ return -2;
+ }
+
+ static int /* Returns 1 for match, else 0 */
+ lx_chk_id (int dev_fd, int host, int channel, int id, int lun)
+ {
+#ifdef SG_GET_SCSI_ID_FOUND
+ struct sg_scsi_id ssid;
+
+ if ((ioctl (dev_fd, SG_GET_SCSI_ID, &ssid) >= 0))
+ {
+ DBG (2, "lx_chk_id: %d,%d %d,%d %d,%d %d,%d\n", host, ssid.host_no,
+ channel, ssid.channel, id, ssid.scsi_id, lun, ssid.lun);
+ if ((host == ssid.host_no) &&
+ (channel == ssid.channel) &&
+ (id == ssid.scsi_id) && (lun == ssid.lun))
+ return 1;
+ else
+ return 0;
+ }
+#endif
+ {
+ struct my_scsi_idlun
+ {
+ int dev_id;
+ int host_unique_id;
+ }
+ my_idlun;
+ if (ioctl (dev_fd, SCSI_IOCTL_GET_IDLUN, &my_idlun) >= 0)
+ {
+ if (((my_idlun.dev_id & 0xff) == id) &&
+ (((my_idlun.dev_id >> 8) & 0xff) == lun) &&
+ (((my_idlun.dev_id >> 16) & 0xff) == channel))
+ return 1; /* cheating, assume 'host' number matches */
+ }
+ }
+ return 0;
+ }
+
+ static int /* Returns 1 if match with 'name' set, else 0 */
+
+ lx_scan_sg (int exclude_devnum, char *name, size_t name_len,
+ int host, int channel, int id, int lun)
+ {
+ int dev_fd, k, missed;
+
+ if (-1 == lx_sg_dev_base)
+ return 0;
+ for (k = 0, missed = 0; (missed < SCAN_MISSES) && (k < 255);
+ ++k, ++missed)
+ {
+ DBG (2, "lx_scan_sg: k=%d, exclude=%d, missed=%d\n", k,
+ exclude_devnum, missed);
+ if (k == exclude_devnum)
+ {
+ missed = 0;
+ continue; /* assumed this one has been checked already */
+ }
+ if ((dev_fd = lx_mk_devicename (k, name, name_len)) >= 0)
+ {
+ missed = 0;
+ if (lx_chk_id (dev_fd, host, channel, id, lun))
+ {
+ close (dev_fd);
+ return 1;
+ }
+ close (dev_fd);
+ }
+ else if (-1 == dev_fd)
+ missed = 0; /* no permissions but something found */
+ }
+ return 0;
+ }
+
+ static int /* Returns 1 if match, else 0 */
+
+ lx_chk_devicename (int guess_devnum, char *name, size_t name_len,
+ int host, int channel, int id, int lun)
+ {
+ int dev_fd;
+
+ if (host < 0)
+ return 0;
+ if (0 != lx_devfs)
+ { /* simple mapping if we have devfs */
+ if (-1 == lx_devfs)
+ {
+ if ((dev_fd =
+ lx_mk_devicename (guess_devnum, name, name_len)) >= 0)
+ close (dev_fd); /* hack to load sg driver module */
+ }
+ snprintf (name, name_len, DEVFS_MSK, host, channel, id, lun);
+ dev_fd = open (name, O_RDWR | O_NONBLOCK);
+ if (dev_fd >= 0)
+ {
+ close (dev_fd);
+ lx_devfs = 1;
+ DBG (1, "lx_chk_devicename: matched device(devfs): %s\n", name);
+ return 1;
+ }
+ else if (ENOENT == errno)
+ lx_devfs = 0;
+ }
+
+ if ((dev_fd = lx_mk_devicename (guess_devnum, name, name_len)) < -1)
+ { /* no candidate sg device file name found, try /dev/sg0,1 */
+ if ((dev_fd = lx_mk_devicename (0, name, name_len)) < -1)
+ {
+ if ((dev_fd = lx_mk_devicename (1, name, name_len)) < -1)
+ return 0; /* no luck finding sg fd to open */
+ }
+ }
+ if (dev_fd >= 0)
+ {
+/* now check this fd for match on <host, channel, id, lun> */
+ if (lx_chk_id (dev_fd, host, channel, id, lun))
+ {
+ close (dev_fd);
+ DBG (1, "lx_chk_devicename: matched device(direct): %s\n", name);
+ return 1;
+ }
+ close (dev_fd);
+ }
+/* if mismatch then call scan algorithm */
+ if (lx_scan_sg (guess_devnum, name, name_len, host, channel, id, lun))
+ {
+ DBG (1, "lx_chk_devicename: matched device(scan): %s\n", name);
+ return 1;
+ }
+ return 0;
+ }
+
+/* Legacy /proc/scsi/scsi */
+static void /* calls 'attach' function pointer with sg device file name iff match */
+sanei_proc_scsi_find_devices (const char *findvendor, const char *findmodel,
+ const char *findtype,
+ int findbus, int findchannel, int findid,
+ int findlun,
+ SANE_Status (*attach) (const char *dev))
+ {
+#define FOUND_VENDOR 1
+#define FOUND_MODEL 2
+#define FOUND_TYPE 4
+#define FOUND_REV 8
+#define FOUND_HOST 16
+#define FOUND_CHANNEL 32
+#define FOUND_ID 64
+#define FOUND_LUN 128
+#define FOUND_ALL 255
+
+ char *me = "sanei_proc_scsi_find_devices";
+
+ size_t findvendor_len = 0, findmodel_len = 0, findtype_len = 0;
+ char vendor[32], model[32], type[32], revision[32];
+ int bus, channel, id, lun;
+
+ int number, i, j, definedd;
+ char line[256], dev_name[128], *c1, *c2, ctmp;
+ const char *string;
+ FILE *proc_fp;
+ char *end;
+ struct
+ {
+ const char *name;
+ size_t name_len;
+ int is_int; /* integer valued? (not a string) */
+ union
+ {
+ void *v; /* avoids compiler warnings... */
+ char *str;
+ int *i;
+ }
+ u;
+ }
+ param[] =
+ {
+ {
+ "Vendor:", 7, 0,
+ {
+ 0}
+ }
+ ,
+ {
+ "Model:", 6, 0,
+ {
+ 0}
+ }
+ ,
+ {
+ "Type:", 5, 0,
+ {
+ 0}
+ }
+ ,
+ {
+ "Rev:", 4, 0,
+ {
+ 0}
+ }
+ ,
+ {
+ "scsi", 4, 1,
+ {
+ 0}
+ }
+ ,
+ {
+ "Channel:", 8, 1,
+ {
+ 0}
+ }
+ ,
+ {
+ "Id:", 3, 1,
+ {
+ 0}
+ }
+ ,
+ {
+ "Lun:", 4, 1,
+ {
+ 0}
+ }
+ };
+
+ param[0].u.str = vendor;
+ param[1].u.str = model;
+ param[2].u.str = type;
+ param[3].u.str = revision;
+ param[4].u.i = &bus;
+ param[5].u.i = &channel;
+ param[6].u.i = &id;
+ param[7].u.i = &lun;
+
+ DBG_INIT ();
+
+ proc_fp = fopen (PROCFILE, "r");
+ if (!proc_fp)
+ {
+ DBG (1, "%s: could not open %s for reading\n", me, PROCFILE);
+ return;
+ }
+
+ number = bus = channel = id = lun = -1;
+
+ vendor[0] = model[0] = type[0] = '\0';
+ if (findvendor)
+ findvendor_len = strlen (findvendor);
+ if (findmodel)
+ findmodel_len = strlen (findmodel);
+ if (findtype)
+ findtype_len = strlen (findtype);
+
+ definedd = 0;
+ while (!feof (proc_fp))
+ {
+ fgets (line, sizeof (line), proc_fp);
+ string = sanei_config_skip_whitespace (line);
+
+ while (*string)
+ {
+ for (i = 0; i < NELEMS (param); ++i)
+ {
+ if (strncmp (string, param[i].name, param[i].name_len) == 0)
+ {
+ string += param[i].name_len;
+ /* Make sure that we don't read the next parameter name
+ as a value, if the real value consists only of spaces
+ */
+ c2 = (char *) (string + strlen (string));
+ for (j = 0; j < NELEMS (param); ++j)
+ {
+ c1 = strstr (string, param[j].name);
+ if ((j != i) && c1 && (c1 < c2))
+ c2 = c1;
+ }
+ ctmp = *c2;
+ *c2 = 0;
+ string = sanei_config_skip_whitespace (string);
+
+ if (param[i].is_int)
+ {
+ if (*string)
+ {
+ *param[i].u.i = strtol (string, &end, 10);
+ string = (char *) end;
+ }
+ else
+ *param[i].u.i = 0;
+ }
+ else
+ {
+ strncpy (param[i].u.str, string, 32);
+ param[i].u.str[31] = '\0';
+ /* while (*string && !isspace (*string))
+ ++string;
+ */
+ }
+ /* string = sanei_config_skip_whitespace (string); */
+ *c2 = ctmp;
+ string = c2;
+ definedd |= 1 << i;
+
+ if (param[i].u.v == &bus)
+ {
+ ++number;
+ definedd = FOUND_HOST;
+ }
+ break;
+ }
+ }
+ if (i >= NELEMS (param))
+ ++string; /* no match */
+ }
+
+ if (FOUND_ALL != definedd)
+ /* some info is still missing */
+ continue;
+
+ definedd = 0;
+ if ((!findvendor || strncmp (vendor, findvendor, findvendor_len) == 0)
+ && (!findmodel || strncmp (model, findmodel, findmodel_len) == 0)
+ && (!findtype || strncmp (type, findtype, findtype_len) == 0)
+ && (findbus == -1 || bus == findbus)
+ && (findchannel == -1 || channel == findchannel)
+ && (findid == -1 || id == findid)
+ && (findlun == -1 || lun == findlun))
+ {
+ DBG (2, "%s: found: vendor=%s model=%s type=%s\n\t"
+ "bus=%d chan=%d id=%d lun=%d num=%d\n",
+ me, findvendor, findmodel, findtype,
+ bus, channel, id, lun, number);
+ if (lx_chk_devicename (number, dev_name, sizeof (dev_name), bus,
+ channel, id, lun)
+ && ((*attach) (dev_name) != SANE_STATUS_GOOD))
+ {
+ DBG(1,"sanei_scsi_find_devices: bad attach\n");
+ }
+ }
+ else
+ {
+ DBG (2, "%s: no match\n", me);
+ }
+ vendor[0] = model[0] = type[0] = 0;
+ bus = channel = id = lun = -1;
+ }
+ fclose (proc_fp);
+ }
+
+#define SYSFS_SCSI_DEVICES "/sys/bus/scsi/devices"
+
+/* From linux/drivers/scsi/scsi.c */
+static char *lnxscsi_device_types[] = {
+ "Direct-Access ",
+ "Sequential-Access",
+ "Printer ",
+ "Processor ",
+ "WORM ",
+ "CD-ROM ",
+ "Scanner ",
+ "Optical Device ",
+ "Medium Changer ",
+ "Communications ",
+ "ASC IT8 ",
+ "ASC IT8 ",
+ "RAID ",
+ "Enclosure ",
+ "Direct-Access-RBC",
+ "Optical card ",
+ "Bridge controller",
+ "Object storage ",
+ "Automation/Drive "
+};
+
+void /* calls 'attach' function pointer with sg device file name iff match */
+sanei_scsi_find_devices (const char *findvendor, const char *findmodel,
+ const char *findtype,
+ int findbus, int findchannel, int findid,
+ int findlun,
+ SANE_Status (*attach) (const char *dev))
+ {
+ char *me = "sanei_scsi_find_devices";
+ char path[PATH_MAX];
+ char dev_name[128];
+ struct dirent buf;
+ struct dirent *de;
+ DIR *scsidevs;
+ FILE *fp;
+ char *ptr;
+ char *end;
+ int bcil[4]; /* bus, channel, id, lun */
+ char vmt[3][33]; /* vendor, model, type */
+ int vmt_len[3];
+ char *vmtfiles[3] = { "vendor", "model", "type" };
+ int lastbus;
+ int number;
+ int i;
+ long val;
+ int ret;
+
+ DBG_INIT ();
+
+ DBG (2, "%s: looking for: v=%s m=%s t=%s b=%d c=%d i=%d l=%d\n",
+ me, findvendor, findmodel, findtype,
+ findbus, findchannel, findid, findlun);
+
+ scsidevs = opendir (SYSFS_SCSI_DEVICES);
+ if (!scsidevs)
+ {
+ DBG (1, "%s: could not open %s; falling back to /proc\n",
+ me, SYSFS_SCSI_DEVICES);
+
+ sanei_proc_scsi_find_devices (findvendor, findmodel, findtype,
+ findbus, findchannel, findid, findlun,
+ attach);
+ return;
+ }
+
+ vmt_len[0] = (findvendor) ? strlen(findvendor) : 0;
+ vmt_len[1] = (findmodel) ? strlen(findmodel) : 0;
+ vmt_len[2] = (findtype) ? strlen(findtype) : 0;
+
+ lastbus = -1;
+ number = -1;
+ for (;;)
+ {
+ ret = readdir_r(scsidevs, &buf, &de);
+ if (ret != 0)
+ {
+ DBG (1, "%s: could not read directory %s: %s\n",
+ me, SYSFS_SCSI_DEVICES, strerror(errno));
+
+ break;
+ }
+
+ if (de == NULL)
+ break;
+
+ if (buf.d_name[0] == '.')
+ continue;
+
+ /* Extract bus, channel, id, lun from directory name b:c:i:l */
+ ptr = buf.d_name;
+ for (i = 0; i < 4; i++)
+ {
+ errno = 0;
+ val = strtol (ptr, &end, 10);
+ if (((errno == ERANGE) && ((val == LONG_MAX) || (val == LONG_MIN)))
+ || ((errno != 0) && (val == 0)))
+ {
+ DBG (1, "%s: invalid integer in string (%s): %s\n",
+ me, ptr, strerror(errno));
+
+ i = 12; /* Skip */
+ break;
+ }
+
+ if (end == ptr)
+ {
+ DBG (1, "%s: no integer found in string: %s (%d)\n", me, ptr, i);
+
+ i = 12; /* Skip */
+ break;
+ }
+
+ if (*end && (*end != ':'))
+ {
+ DBG (1, "%s: parse error on string %s (%d)\n", me, buf.d_name, i);
+
+ i = 12; /* Skip */
+ break;
+ }
+
+ if (val > INT_MAX)
+ {
+ DBG (1, "%s: integer value too large (%s)\n", me, buf.d_name);
+
+ i = 12; /* Skip */
+ break;
+ }
+
+ bcil[i] = (int) val;
+ ptr = end + 1;
+ }
+
+ /* Skip this one */
+ if (i == 12)
+ continue;
+
+ if (bcil[0] != lastbus)
+ {
+ lastbus = bcil[0];
+ number++;
+ }
+
+ for (i = 0; i < 3; i++)
+ {
+ ret = snprintf (path, PATH_MAX, "%s/%s/%s",
+ SYSFS_SCSI_DEVICES, buf.d_name, vmtfiles[i]);
+ if ((ret < 0) || (ret >= PATH_MAX))
+ {
+ DBG (1, "%s: skipping %s/%s, PATH_MAX exceeded on %s\n",
+ me, SYSFS_SCSI_DEVICES, buf.d_name, vmtfiles[i]);
+
+ i = 12; /* Skip */
+ break;
+ }
+
+ memset (vmt[i], 0, sizeof(vmt[i]));
+
+ fp = fopen(path, "r");
+ if (!fp)
+ {
+ DBG (1, "%s: could not open %s: %s\n", me, path, strerror(errno));
+
+ i = 12; /* Skip */
+ break;
+ }
+
+ ret = fread (vmt[i], 1, sizeof(vmt[i]) - 1, fp);
+ if (ret <= 0)
+ {
+ if (ferror(fp))
+ {
+ DBG (1, "%s: error reading %s\n", me, path);
+
+ i = 12; /* Skip */
+ break;
+ }
+ }
+
+ if (vmt[i][ret - 1] == '\n')
+ vmt[i][ret - 1] = '\0';
+
+ fclose (fp);
+ }
+
+ /* Skip this one */
+ if (i == 12)
+ continue;
+
+ /* Type is a numeric string and must be converted back to a well-known string */
+ errno = 0;
+ val = strtol (vmt[2], &end, 10);
+ if (((errno == ERANGE) && ((val == LONG_MAX) || (val == LONG_MIN)))
+ || ((errno != 0) && (val == 0)))
+ {
+ DBG (1, "%s: invalid integer in type string (%s): %s\n",
+ me, vmt[2], strerror(errno));
+ continue;
+ }
+
+ if (end == vmt[2])
+ {
+ DBG (1, "%s: no integer found in type string: %s\n", me, vmt[2]);
+ continue;
+ }
+
+ if ((val < 0) || (val >= (int)(sizeof(lnxscsi_device_types) / sizeof(lnxscsi_device_types[0]))))
+ {
+ DBG (1, "%s: invalid type %ld\n", me, val);
+ continue;
+ }
+
+ strncpy(vmt[2], lnxscsi_device_types[val], sizeof(vmt[2]) - 1);
+
+ if ((!findvendor || strncmp (vmt[0], findvendor, vmt_len[0]) == 0)
+ && (!findmodel || strncmp (vmt[1], findmodel, vmt_len[1]) == 0)
+ && (!findtype || strncmp (vmt[2], findtype, vmt_len[2]) == 0)
+ && (findbus == -1 || bcil[0] == findbus)
+ && (findchannel == -1 || bcil[1] == findchannel)
+ && (findid == -1 || bcil[2] == findid)
+ && (findlun == -1 || bcil[3] == findlun))
+ {
+ DBG (2, "%s: found: vendor=%s model=%s type=%s\n\t"
+ "bus=%d chan=%d id=%d lun=%d num=%d\n",
+ me, vmt[0], vmt[1], vmt[2],
+ bcil[0], bcil[1], bcil[2], bcil[3], number);
+
+ if (lx_chk_devicename (number, dev_name, sizeof (dev_name),
+ bcil[0], bcil[1], bcil[2], bcil[3])
+ && ((*attach) (dev_name) != SANE_STATUS_GOOD))
+ {
+ DBG (1, "%s: bad attach\n", me);
+ }
+ }
+ else
+ {
+ DBG (2, "%s: no match\n", me);
+ }
+ }
+
+ closedir(scsidevs);
+ }
+
+#endif /* USE == LINUX_INTERFACE */
+
+
+#if USE == BSD_INTERFACE
+
+#ifndef HAVE_SCSIREQ_ENTER
+ static int scsireq_enter (int fd, scsireq_t * hdr)
+ {
+ return ioctl (fd, SCIOCCOMMAND, hdr);
+ }
+#endif /* !HAVE_SCSIREQ_ENTER */
+
+ SANE_Status
+ sanei_scsi_cmd2 (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+ /* xxx obsolete: size_t cdb_size;
+ */
+ scsireq_t hdr;
+ int result;
+
+/* xxx obsolete:
+ cdb_size = CDB_SIZE (*(u_char *) src);
+*/
+
+ memset (&hdr, 0, sizeof (hdr));
+ memcpy (hdr.cmd, cmd, cmd_size);
+ if (dst_size && *dst_size)
+ {
+ /* xxx obsolete: assert (cdb_size == src_size);
+ */
+ hdr.flags = SCCMD_READ;
+ hdr.databuf = dst;
+ hdr.datalen = *dst_size;
+ }
+ else
+ {
+ /* xxx obsolete: assert (cdb_size <= src_size);
+ */
+ hdr.flags = SCCMD_WRITE;
+ /* The old variant:
+ hdr.databuf = (char *) src + cdb_size;
+ hdr.datalen = src_size;
+ xxxxxx huh? Shouldn't the above line have been src_size - cdb_size)
+ */
+ hdr.databuf = (char *) src;
+ hdr.datalen = src_size;
+ }
+ hdr.timeout = sane_scsicmd_timeout * 1000;
+ hdr.cmdlen = cmd_size;
+ hdr.senselen = sizeof (hdr.sense);
+
+ result = scsireq_enter (fd, &hdr);
+ if (result < 0)
+ {
+ DBG (1, "sanei_scsi_cmd: scsi_reqenter() failed: %s\n",
+ strerror (errno));
+ return SANE_STATUS_IO_ERROR;
+ }
+ if (hdr.retsts != SCCMD_OK)
+ {
+ SANEI_SCSI_Sense_Handler handler;
+
+ DBG (1, "sanei_scsi_cmd: scsi returned with status %d\n", hdr.retsts);
+ switch (hdr.retsts)
+ {
+ case SCCMD_TIMEOUT:
+ case SCCMD_BUSY:
+ return SANE_STATUS_DEVICE_BUSY;
+
+ case SCCMD_SENSE:
+ handler = fd_info[fd].sense_handler;
+ if (handler)
+ return (*handler) (fd, &hdr.sense[0],
+ fd_info[fd].sense_handler_arg);
+ /* fall through */
+ default:
+ return SANE_STATUS_IO_ERROR;
+ }
+ }
+
+ if (dst_size)
+ *dst_size = hdr.datalen_used;
+
+ return SANE_STATUS_GOOD;
+ }
+#endif /* USE == BSD_INTERFACE */
+
+#if USE == FREEBSD_CAM_INTERFACE
+ SANE_Status sanei_scsi_cmd2 (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+
+ struct cam_device *dev;
+ union ccb *ccb;
+ int rv;
+ u_int32_t ccb_flags;
+ char *data_buf;
+ size_t data_len;
+ SANE_Status status;
+
+ if (fd < 0 || fd > CAM_MAXDEVS || cam_devices[fd] == NULL)
+ {
+ fprintf (stderr, "attempt to reference invalid unit %d\n", fd);
+ return SANE_STATUS_INVAL;
+ }
+
+ dev = cam_devices[fd];
+ ccb = cam_getccb (dev);
+
+ /* Build the CCB */
+ bzero (&(&ccb->ccb_h)[1], sizeof (struct ccb_scsiio));
+ bcopy (cmd, &ccb->csio.cdb_io.cdb_bytes, cmd_size);
+
+ /*
+ * Set the data direction flags.
+ */
+ if (dst_size && *dst_size)
+ {
+ /* xxx obsolete: assert (cdb_size == src_size);
+ */
+ ccb_flags = CAM_DIR_IN;
+ data_buf = ((char *) (dst));
+ data_len = *dst_size;
+ }
+ else if (src_size > 0)
+ {
+ ccb_flags = CAM_DIR_OUT;
+ data_buf = ((char *) (src));
+ data_len = src_size;
+ }
+ else
+ {
+ ccb_flags = CAM_DIR_NONE;
+ data_buf = NULL;
+ data_len = 0;
+ }
+
+ cam_fill_csio (&ccb->csio,
+ /* retries */ 1,
+ /* cbfncp */ NULL,
+ /* flags */ ccb_flags,
+ /* tag_action */ MSG_SIMPLE_Q_TAG,
+ /* data_ptr */ (u_int8_t *) data_buf,
+ /* dxfer_len */ data_len,
+ /* sense_len */ SSD_FULL_SIZE,
+ /* cdb_len */ cmd_size,
+ /* timeout */ sane_scsicmd_timeout * 1000);
+
+ /* Run the command */
+ errno = 0;
+ if ((rv = cam_send_ccb (dev, ccb)) == -1)
+ {
+ cam_freeccb (ccb);
+ return (SANE_STATUS_IO_ERROR);
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)
+ {
+ SANEI_SCSI_Sense_Handler handler;
+
+ DBG (1, "sanei_scsi_cmd: scsi returned with status %d\n",
+ (ccb->ccb_h.status & CAM_STATUS_MASK));
+
+ switch (ccb->ccb_h.status & CAM_STATUS_MASK)
+ {
+ case CAM_BUSY:
+ case CAM_SEL_TIMEOUT:
+ case CAM_SCSI_BUSY:
+ status = SANE_STATUS_DEVICE_BUSY;
+ break;
+ default:
+ status = SANE_STATUS_IO_ERROR;
+ }
+
+ handler = fd_info[fd].sense_handler;
+ if (handler && (ccb->ccb_h.status & CAM_AUTOSNS_VALID))
+ {
+ SANE_Status st = (*handler)
+ (fd, ((u_char *) (&ccb->csio.sense_data)),
+ fd_info[fd].sense_handler_arg);
+ cam_freeccb (ccb);
+ return st;
+ }
+ else
+ {
+ cam_freeccb (ccb);
+ return status;
+ }
+ }
+ cam_freeccb (ccb);
+ return SANE_STATUS_GOOD;
+ }
+
+#define WE_HAVE_FIND_DEVICES
+
+ int
+ cam_compare_inquiry (int fd, path_id_t path_id,
+ target_id_t target_id, lun_id_t target_lun,
+ const char *vendor, const char *product,
+ const char *type)
+ {
+ struct ccb_dev_match cdm;
+ struct device_match_pattern *pattern;
+ struct scsi_inquiry_data *inq;
+ int retval = 0;
+
+ /* build ccb for device match */
+ bzero (&cdm, sizeof (cdm));
+ cdm.ccb_h.func_code = XPT_DEV_MATCH;
+
+ /* result buffer */
+ cdm.match_buf_len = sizeof (struct dev_match_result);
+ cdm.matches = (struct dev_match_result *) malloc (cdm.match_buf_len);
+ cdm.num_matches = 0;
+
+ /* pattern buffer */
+ cdm.num_patterns = 1;
+ cdm.pattern_buf_len = sizeof (struct dev_match_pattern);
+ cdm.patterns = (struct dev_match_pattern *) malloc (cdm.pattern_buf_len);
+
+ /* assemble conditions */
+ cdm.patterns[0].type = DEV_MATCH_DEVICE;
+ pattern = &cdm.patterns[0].pattern.device_pattern;
+ pattern->flags = DEV_MATCH_PATH | DEV_MATCH_TARGET | DEV_MATCH_LUN;
+ pattern->path_id = path_id;
+ pattern->target_id = target_id;
+ pattern->target_lun = target_lun;
+
+ if (ioctl (fd, CAMIOCOMMAND, &cdm) == -1)
+ {
+ DBG (1, "error sending CAMIOCOMMAND ioctl");
+ retval = -1;
+ goto ret;
+ }
+
+ if ((cdm.ccb_h.status != CAM_REQ_CMP)
+ || ((cdm.status != CAM_DEV_MATCH_LAST)
+ && (cdm.status != CAM_DEV_MATCH_MORE)))
+ {
+ DBG (1, "got CAM error %#x, CDM error %d\n",
+ cdm.ccb_h.status, cdm.status);
+ retval = -1;
+ goto ret;
+ }
+
+ if (cdm.num_matches == 0)
+ {
+ DBG (1, "not found\n");
+ retval = -1;
+ goto ret;
+ }
+
+ if (cdm.matches[0].type != DEV_MATCH_DEVICE)
+ {
+ DBG (1, "no device match\n");
+ retval = -1;
+ goto ret;
+ }
+
+ inq = &cdm.matches[0].result.device_result.inq_data;
+ if ((vendor && cam_strmatch (inq->vendor, vendor, SID_VENDOR_SIZE)) ||
+ (product && cam_strmatch (inq->product, product, SID_PRODUCT_SIZE)))
+ retval = 1;
+
+ ret:
+ free (cdm.patterns);
+ free (cdm.matches);
+ return (retval);
+ }
+
+ void
+ sanei_scsi_find_devices (const char *findvendor, const char *findmodel,
+ const char *findtype,
+ int findbus, int findchannel, int findid,
+ int findlun,
+ SANE_Status (*attach) (const char *dev))
+ {
+ int fd;
+ struct ccb_dev_match cdm;
+ struct periph_match_pattern *pattern;
+ struct periph_match_result *result;
+ int i;
+ char devname[16];
+
+ DBG_INIT ();
+
+ if ((fd = open (XPT_DEVICE, O_RDWR)) == -1)
+ {
+ DBG (1, "could not open %s\n", XPT_DEVICE);
+ return;
+ }
+
+ /* build ccb for device match */
+ bzero (&cdm, sizeof (cdm));
+ cdm.ccb_h.func_code = XPT_DEV_MATCH;
+
+ /* result buffer */
+ cdm.match_buf_len = sizeof (struct dev_match_result) * 100;
+ cdm.matches = (struct dev_match_result *) malloc (cdm.match_buf_len);
+ cdm.num_matches = 0;
+
+ /* pattern buffer */
+ cdm.num_patterns = 1;
+ cdm.pattern_buf_len = sizeof (struct dev_match_pattern);
+ cdm.patterns = (struct dev_match_pattern *) malloc (cdm.pattern_buf_len);
+
+ /* assemble conditions ... findchannel is ignored */
+ cdm.patterns[0].type = DEV_MATCH_PERIPH;
+ pattern = &cdm.patterns[0].pattern.periph_pattern;
+ pattern->flags = PERIPH_MATCH_NAME;
+ strcpy (pattern->periph_name, "pass");
+ if (findbus != -1)
+ {
+ pattern->path_id = findbus;
+ pattern->flags |= PERIPH_MATCH_PATH;
+ }
+ if (findid != -1)
+ {
+ pattern->target_id = findid;
+ pattern->flags |= PERIPH_MATCH_TARGET;
+ }
+ if (findlun != -1)
+ {
+ pattern->target_lun = findlun;
+ pattern->flags |= PERIPH_MATCH_LUN;
+ }
+
+ /* result loop */
+ do
+ {
+ if (ioctl (fd, CAMIOCOMMAND, &cdm) == -1)
+ {
+ DBG (1, "error sending CAMIOCOMMAND ioctl");
+ break;
+ }
+
+ if ((cdm.ccb_h.status != CAM_REQ_CMP)
+ || ((cdm.status != CAM_DEV_MATCH_LAST)
+ && (cdm.status != CAM_DEV_MATCH_MORE)))
+ {
+ DBG (1, "got CAM error %#x, CDM error %d\n",
+ cdm.ccb_h.status, cdm.status);
+ break;
+ }
+
+ for (i = 0; i < cdm.num_matches; i++)
+ {
+ if (cdm.matches[i].type != DEV_MATCH_PERIPH)
+ continue;
+ result = &cdm.matches[i].result.periph_result;
+ DBG (4, "%s%d on scbus%d %d:%d\n",
+ result->periph_name, result->unit_number,
+ result->path_id, result->target_id, result->target_lun);
+ if (cam_compare_inquiry (fd, result->path_id,
+ result->target_id, result->target_lun,
+ findvendor, findmodel, findtype) == 0)
+ {
+ sprintf (devname, "/dev/%s%d", result->periph_name,
+ result->unit_number);
+ (*attach) (devname);
+ }
+ }
+ }
+ while ((cdm.ccb_h.status == CAM_REQ_CMP)
+ && (cdm.status == CAM_DEV_MATCH_MORE));
+
+ free (cdm.patterns);
+ free (cdm.matches);
+ close (fd);
+ return;
+ }
+
+#endif
+
+
+
+#if USE == HPUX_INTERFACE
+/* XXX untested code! */
+ SANE_Status
+ sanei_scsi_cmd2 (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+ struct sctl_io hdr;
+ /* xxx obsolete size_t cdb_size;
+
+ cdb_size = CDB_SIZE (*(u_char *) src);
+ */
+
+ memset (&hdr, 0, sizeof (hdr));
+ memcpy (hdr.cdb, cmd, cmd_size);
+ if (dst_size && *dst_size)
+ {
+ /* xxx obsolete assert (cdb_size == src_size);
+ */
+ hdr.flags = SCTL_READ;
+ hdr.data = dst;
+ hdr.data_length = *dst_size;
+ }
+ else
+ {
+ /* xxx obsolete assert (cdb_size <= src_size);
+ */
+ hdr.data = (char *) src;
+ hdr.data_length = src_size;
+ }
+ hdr.cdb_length = cmd_size;
+ hdr.max_msecs = sane_scsicmd_timeout * 1000;
+ if (ioctl (fd, SIOC_IO, &hdr) < 0)
+ {
+ DBG (1, "sanei_scsi_cmd: ioctl(SIOC_IO) failed: %s\n",
+ strerror (errno));
+ return SANE_STATUS_IO_ERROR;
+ }
+ if (hdr.cdb_status)
+ DBG (1, "sanei_scsi_cmd: SCSI completed with cdb_status=%d\n",
+ hdr.cdb_status);
+ if (dst_size)
+ *dst_size = hdr.data_xfer;
+
+ if (hdr.sense_xfer > 0 && (hdr.sense[0] & 0x80)
+ && fd_info[fd].sense_handler)
+ return (*fd_info[fd].sense_handler) (fd, hdr.sense,
+ fd_info[fd].sense_handler_arg);
+ return SANE_STATUS_GOOD;
+ }
+#endif /* USE == HPUX_INTERFACE */
+
+
+#if USE == OPENSTEP_INTERFACE
+ SANE_Status
+ sanei_scsi_cmd2 (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+ struct scsi_req hdr;
+ /* xxx obsolete size_t cdb_size;
+
+ cdb_size = CDB_SIZE (*(u_char *) src);
+ */
+
+ memset (&hdr, 0, sizeof (hdr));
+ memcpy (&hdr.sr_cdb, cmd, cmd_size);
+ hdr.sr_cdb_length = cmd_size;
+
+ if (dst_size && *dst_size)
+ {
+ /* xxx obsolete assert (cdb_size == src_size);
+ */
+ hdr.sr_dma_dir = SR_DMA_RD;
+ hdr.sr_addr = dst;
+ hdr.sr_dma_max = *dst_size;
+ }
+ else
+ {
+ /* xxx obsolete assert (cdb_size <= src_size);
+ */
+ hdr.sr_dma_dir = SR_DMA_WR;
+ hdr.sr_addr = (char *) src;
+ hdr.sr_dma_max = src_size;
+ }
+ hdr.sr_ioto = sane_scsicmd_timeout;
+
+ if (ioctl (fd, SGIOCREQ, &hdr) == -1)
+ {
+ DBG (1, "sanei_scsi_cmd: ioctl(SGIOCREQ) failed: %s\n",
+ strerror (errno));
+ return SANE_STATUS_IO_ERROR;
+ }
+ if (hdr.sr_io_status != 1)
+ DBG (1, "sanei_scsi_cmd: SGIOCREQ completed with sr_io_status=%d\n",
+ hdr.sr_io_status);
+
+ if (hdr.sr_io_status == SR_IOST_CHKSNV)
+ {
+ struct scsi_req sr;
+ struct cdb_6 *cdbp = &sr.sr_cdb.cdb_c6;
+ struct esense_reply sense_reply;
+ int i;
+ char *p;
+
+ /* clear struct */
+ p = (char *) cdbp;
+ for (i = 0; i < sizeof (union cdb); i++)
+ *p++ = 0;
+ memset (&sr, 0, sizeof (struct scsi_req));
+
+ cdbp->c6_opcode = C6OP_REQSENSE;
+ cdbp->c6_lun = 0; /* where do I get the lun from? */
+ cdbp->c6_len = 0x20;
+ cdbp->c6_ctrl = 0;
+
+ sr.sr_dma_dir = SR_DMA_RD;
+ sr.sr_addr = (char *) &sense_reply;
+ sr.sr_dma_max = sizeof (struct esense_reply);
+ sr.sr_ioto = sane_scsicmd_timeout;
+ sr.sr_cdb_length = 6;
+
+ ioctl (fd, SGIOCREQ, &sr);
+ if (sense_reply.er_ibvalid)
+ {
+ sr.sr_esense = sense_reply;
+ if (fd_info[fd].sense_handler)
+ return (*fd_info[fd].sense_handler)
+ (fd, (u_char *) & sr.sr_esense,
+ fd_info[fd].sense_handler_arg);
+ }
+
+ /* sense reply is invalid */
+ return SANE_STATUS_INVAL;
+ }
+
+ if (hdr.sr_scsi_status == SR_IOST_CHKSV && fd_info[fd].sense_handler)
+ return (*fd_info[fd].sense_handler) (fd, (u_char *) & hdr.sr_esense,
+ fd_info[fd].sense_handler_arg);
+ if (dst_size)
+ *dst_size = hdr.sr_dma_xfr;
+ return SANE_STATUS_GOOD;
+ }
+#endif /* USE == OPENSTEP_INTERFACE */
+
+
+#if USE == DECUNIX_INTERFACE
+ SANE_Status
+ sanei_scsi_cmd2 (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+ u_char sense[64];
+ UAGT_CAM_CCB hdr;
+ CCB_SCSIIO ccb;
+ /* xxx obsolete size_t cdb_size;
+
+ cdb_size = CDB_SIZE (*(u_char *) src);
+ */
+
+ memset (&ccb, 0, sizeof (ccb));
+ ccb.cam_ch.my_addr = (CCB_HEADER *) & ccb;
+ ccb.cam_ch.cam_ccb_len = sizeof (ccb);
+ ccb.cam_ch.cam_func_code = XPT_SCSI_IO;
+ ccb.cam_ch.cam_path_id = fd_info[fd].bus;
+ ccb.cam_ch.cam_target_id = fd_info[fd].target;
+ ccb.cam_ch.cam_target_lun = fd_info[fd].lun;
+ ccb.cam_ch.cam_flags = 0;
+
+ if (dst_size && *dst_size)
+ {
+ /* xxx obsolete assert (cdb_size == src_size);
+ */
+ ccb.cam_ch.cam_flags |= CAM_DIR_IN;
+ ccb.cam_data_ptr = (u_char *) dst;
+ ccb.cam_dxfer_len = *dst_size;
+ }
+ else
+ {
+ /* xxx obsolete assert (cdb_size <= src_size);
+ */
+ if (0 == src_size)
+ ccb.cam_ch.cam_flags |= CAM_DIR_NONE;
+ else
+ ccb.cam_ch.cam_flags |= CAM_DIR_OUT;
+ ccb.cam_data_ptr = (u_char *) src;
+ ccb.cam_dxfer_len = src_size;
+ }
+ ccb.cam_timeout = sane_scsicmd_timeout;
+ ccb.cam_cdb_len = cmd_size;
+ memcpy (&ccb.cam_cdb_io.cam_cdb_bytes[0], cmd, cmd_size);
+
+ memset (&hdr, 0, sizeof (hdr));
+ hdr.uagt_ccb = (CCB_HEADER *) & ccb;
+ hdr.uagt_ccblen = sizeof (ccb);
+ hdr.uagt_buffer = ccb.cam_data_ptr;
+ hdr.uagt_buflen = ccb.cam_dxfer_len;
+ hdr.uagt_snsbuf = sense;
+ hdr.uagt_snslen = sizeof (sense);
+ hdr.uagt_cdb = 0; /* indicate that CDB is in CCB */
+ hdr.uagt_cdblen = 0;
+
+ if (ioctl (cam_fd, UAGT_CAM_IO, &hdr) < 0)
+ {
+ DBG (1, "sanei_scsi_cmd: ioctl(UAGT_CAM_IO) failed: %s\n",
+ strerror (errno));
+ return SANE_STATUS_IO_ERROR;
+ }
+ if (ccb.cam_ch.cam_status != CAM_REQ_CMP)
+ {
+ DBG (1, "sanei_scsi_cmd: UAGT_CAM_IO completed with cam_status=%d\n",
+ ccb.cam_ch.cam_status);
+
+ if (ccb.cam_ch.cam_status == CAM_AUTOSNS_VALID
+ && fd_info[fd].sense_handler)
+ return (*fd_info[fd].sense_handler) (fd, sense,
+ fd_info[fd].sense_handler_arg);
+ else
+ return SANE_STATUS_INVAL;
+ }
+ if (dst_size)
+ *dst_size = ccb.cam_dxfer_len;
+ return SANE_STATUS_GOOD;
+ }
+#endif /* USE == DECUNIX_INTERFACE */
+
+
+#if USE == SCO_OS5_INTERFACE
+ SANE_Status
+ sanei_scsi_cmd2 (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+ static u_char sense_buffer[256];
+ struct scsicmd2 sc2;
+ struct scsicmd *sc;
+ /* xxx obsolete int cdb_size;
+ */
+ int opcode;
+ int i;
+
+ if (fd < 0)
+ return SANE_STATUS_IO_ERROR;
+
+ memset (&sc2, 0, sizeof (sc2));
+ sc = &sc2.cmd;
+ sc2.sense_len = sizeof (sense_buffer);
+ sc2.sense_ptr = sense_buffer;
+
+ /* xxx obsolete cdb_size = CDB_SIZE (*(u_char *) src);
+ */
+ if (dst_size && *dst_size)
+ {
+ sc->is_write = 0;
+ sc->data_ptr = dst;
+ sc->data_len = *dst_size;
+ }
+ else
+ {
+ sc->data_len = src_size;
+ sc->data_ptr = (char *) src;
+ sc->is_write = 1;
+ }
+ memcpy (sc->cdb, cmd, cmd_size);
+ sc->cdb_len = cmd_size;
+
+ /* Send the command down via the "pass-through" interface */
+ if (ioctl (fd, SCSIUSERCMD2, &sc2) < 0)
+ {
+ DBG (1, "sanei_scsi_cmd: ioctl(SCSIUSERCMD2) failed: %s\n",
+ strerror (errno));
+ return SANE_STATUS_IO_ERROR;
+ }
+ if (sc->host_sts || sc->target_sts)
+ {
+ DBG (1, "sanei_scsi_cmd: SCSIUSERCMD2 completed with "
+ "host_sts=%x, target_sts=%x\n", sc->host_sts, sc->target_sts);
+ if (fd_info[fd].sense_handler)
+ return (*fd_info[fd].sense_handler) (fd, sense_buffer,
+ fd_info[fd].sense_handler_arg);
+ return SANE_STATUS_IO_ERROR;
+ }
+ return SANE_STATUS_GOOD;
+ }
+#endif /* USE == SCO_OS5_INTERFACE */
+#if USE == SYSVR4_INTERFACE
+
+/*
+ * UNIXWARE 2.x interface
+ * (c) R=I+S Rapp Informatik System Germany
+ * Email: wolfgang@rapp-informatik.de
+ *
+ * The driver version should run with other scsi componets like disk
+ * attached to the same controller at the same time.
+ *
+ * Attention : This port needs a sane kernel driver for Unixware 2.x
+ * The driver is available in binary pkgadd format
+ * Plese mail me.
+ *
+ */
+ SANE_Status
+ sanei_scsi_cmd2 (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+ struct sb sb, *sb_ptr; /* Command block and pointer */
+ struct scs *scs; /* group 6 command pointer */
+ struct scm *scm; /* group 10 command pointer */
+ struct scv *scv; /* group 12 command pointer */
+ char sense[32]; /* for call of sens req */
+ char cmd[16]; /* global for right alignment */
+ char *cp;
+
+ /* xxx obsolete size_t cdb_size;
+
+ cdb_size = CDB_SIZE (*(u_char *) src);
+ */
+ memset (&cmd, 0, 16);
+ sb_ptr = &sb;
+ sb_ptr->sb_type = ISCB_TYPE;
+ cp = (char *) cmd;
+ DBG (1,
+ "cdb_size = %d src = {0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x ...}\n",
+ cmd_size, cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7],
+ cp[8], cp[9]);
+ switch (cmd_size)
+ {
+ default:
+ return SANE_STATUS_IO_ERROR;
+ case 6:
+ scs = (struct scs *) cmd;
+ memcpy (SCS_AD (scs), cmd, SCS_SZ);
+ scs->ss_lun = 0;
+ sb_ptr->SCB.sc_cmdpt = SCS_AD (scs);
+ sb_ptr->SCB.sc_cmdsz = SCS_SZ;
+ break;
+ case 10:
+ scm = (struct scm *) cmd;
+ memcpy (SCM_AD (scm), cmd, SCM_SZ);
+ scm->sm_lun = 0;
+ sb_ptr->SCB.sc_cmdpt = SCM_AD (scm);
+ sb_ptr->SCB.sc_cmdsz = SCM_SZ;
+ break;
+ case 12:
+ scv = (struct scv *) cmd;
+ memcpy (SCV_AD (scv), cmd, SCV_SZ);
+ scv->sv_lun = 0;
+ sb_ptr->SCB.sc_cmdpt = SCV_AD (scv);
+ sb_ptr->SCB.sc_cmdsz = SCV_SZ;
+ break;
+ }
+ if (dst_size && *dst_size)
+ {
+ assert (0 == src_size);
+ sb_ptr->SCB.sc_mode = SCB_READ;
+ sb_ptr->SCB.sc_datapt = dst;
+ sb_ptr->SCB.sc_datasz = *dst_size;
+ }
+ else
+ {
+ assert (0 <= src_size);
+ sb_ptr->SCB.sc_mode = SCB_WRITE;
+ sb_ptr->SCB.sc_datapt = (char *) src;
+ if ((sb_ptr->SCB.sc_datasz = src_size) > 0)
+ {
+ sb_ptr->SCB.sc_mode = SCB_WRITE;
+ }
+ else
+ {
+ /* also use READ mode if the backends have write with length 0 */
+ sb_ptr->SCB.sc_mode = SCB_READ;
+ }
+ }
+ sb_ptr->SCB.sc_time = sane_scsicmd_timeout * 1000;
+ DBG (1, "sanei_scsi_cmd: sc_mode = %d, sc_cmdsz = %d, sc_datasz = %d\n",
+ sb_ptr->SCB.sc_mode, sb_ptr->SCB.sc_cmdsz, sb_ptr->SCB.sc_datasz);
+ {
+ /* do read write by normal read or write system calls */
+ /* the driver will lock process in momory and do optimized transfer */
+ cp = (char *) cmd;
+ switch (*cp)
+ {
+ case 0x0: /* test unit ready */
+ if (ioctl (fd, SS_TEST, NULL) < 0)
+ {
+ return SANE_STATUS_DEVICE_BUSY;
+ }
+ break;
+ case SS_READ:
+ case SM_READ:
+ if (*dst_size > 0x2048)
+ {
+ sb_ptr->SCB.sc_datapt = NULL;
+ sb_ptr->SCB.sc_datasz = 0;
+ if (memcmp
+ (sb_ptr->SCB.sc_cmdpt, lastrcmd, sb_ptr->SCB.sc_cmdsz))
+ {
+ /* set the command block for the next read or write */
+ memcpy (lastrcmd, sb_ptr->SCB.sc_cmdpt,
+ sb_ptr->SCB.sc_cmdsz);
+ if (!ioctl (fd, SDI_SEND, sb_ptr))
+ {
+ *dst_size = read (fd, dst, *dst_size);
+ if (*dst_size == -1)
+ {
+ perror ("sanei-scsi:UW-driver read ");
+ return SANE_STATUS_IO_ERROR;
+ }
+ break;
+ }
+ }
+ else
+ {
+ *dst_size = read (fd, dst, *dst_size);
+ if (*dst_size == -1)
+ {
+ perror ("sanei-scsi:UW-driver read ");
+ return SANE_STATUS_IO_ERROR;
+ }
+ break;
+ }
+ return SANE_STATUS_IO_ERROR;
+ }
+ /* fall through for small read */
+ default:
+ if (ioctl (fd, SDI_SEND, sb_ptr) < 0)
+ {
+ DBG (1, "sanei_scsi_cmd: ioctl(SDI_SEND) FAILED: %s\n",
+ strerror (errno));
+ return SANE_STATUS_IO_ERROR;
+ }
+ if (dst_size)
+ *dst_size = sb_ptr->SCB.sc_datasz;
+#ifdef UWSUPPORTED /* at this time not supported by driver */
+ if (sb_ptr->SCB.sc_comp_code != SDI_ASW)
+ {
+ DBG (1, "sanei_scsi_cmd: scsi_cmd failture %x\n",
+ sb_ptr->SCB.sc_comp_code);
+ if (sb_ptr->SCB.sc_comp_code == SDI_CKSTAT
+ && sb_ptr->SCB.sc_status == S_CKCON)
+ if (fd_info[fd].sense_handler)
+ {
+ void *arg = fd_info[fd].sense_handler_arg;
+ return (*fd_info[fd].sense_handler) (fd,
+ (u_char *) & sb_ptr->
+ SCB.sc_link, arg);
+ }
+ return SANE_STATUS_IO_ERROR;
+ }
+#endif
+ break;
+ }
+ return SANE_STATUS_GOOD;
+ }
+ }
+#endif /* USE == SYSVR4_INTERFACE */
+#if USE == SCO_UW71_INTERFACE
+ SANE_Status
+ sanei_scsi_cmd2 (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+ static u_char sense_buffer[24];
+ struct scb cmdblk;
+ time_t elapsed;
+ uint_t compcode, status;
+ /* xxx obsolete int cdb_size, mode;
+ */
+ int mode;
+ int i;
+
+ if (fd < 0)
+ return SANE_STATUS_IO_ERROR;
+
+ cmdblk.sc_cmdpt = (caddr_t) cmd;
+ /* xxx obsolete cdb_size = CDB_SIZE (*(u_char *) src);
+ */
+ cmdblk.sc_cmdsz = cmd_size;
+ cmdblk.sc_time = 60000; /* 60 secs */
+
+ if (dst_size && *dst_size)
+ {
+ /* xxx obsolete assert (cdb_size == src_size);
+ */
+ cmdblk.sc_datapt = (caddr_t) dst;
+ cmdblk.sc_datasz = *dst_size;
+ mode = SCB_READ;
+ }
+ else
+ {
+ /* xxx obsolete assert (cdb_size <= src_size);
+ */
+ cmdblk.sc_datapt = (char *) src;
+ cmdblk.sc_datasz = src_size;
+ mode = SCB_WRITE;
+ }
+
+ if (pt_send (fd, cmdblk.sc_cmdpt, cmdblk.sc_cmdsz, cmdblk.sc_datapt,
+ cmdblk.sc_datasz, mode, cmdblk.sc_time, &elapsed, &compcode,
+ &status, sense_buffer, sizeof (sense_buffer)) != 0)
+ {
+ DBG (1, "sanei_scsi_cmd: pt_send failed: %s!\n", strerror (errno));
+ }
+ else
+ {
+ DBG (2, "sanei_scsi_cmd completed with: compcode = %x, status = %x\n",
+ compcode, status);
+
+ switch (compcode)
+ {
+ case SDI_ASW: /* All seems well */
+ return SANE_STATUS_GOOD;
+ case SDI_CKSTAT:
+ DBG (2, "Sense Data:\n");
+ for (i = 0; i < sizeof (sense_buffer); i++)
+ DBG (2, "%.2X ", sense_buffer[i]);
+ DBG (2, "\n");
+ if (fd_info[fd].sense_handler)
+ return (*fd_info[fd].sense_handler) (fd, sense_buffer,
+ fd_info[fd].
+ sense_handler_arg);
+ /* fall through */
+ default:
+ return SANE_STATUS_IO_ERROR;
+ }
+ }
+ }
+#endif /* USE == SCO_UW71_INTERFACE */
+
+#if USE == OS2_INTERFACE
+
+#define WE_HAVE_FIND_DEVICES
+
+ static int
+ get_devicename (int bus, int target, int lun, char *name, size_t name_len)
+ {
+ snprintf (name, name_len, "b%dt%dl%d", bus, target, lun);
+ DBG (1, "OS/2 searched device is %s\n", name);
+ return 0;
+ }
+
+ void
+ sanei_scsi_find_devices (const char *findvendor, const char *findmodel,
+ const char *findtype,
+ int findbus, int findchannel, int findid,
+ int findlun,
+ SANE_Status (*attach) (const char *dev))
+ {
+ size_t findvendor_len = 0, findmodel_len = 0, findtype_len = 0;
+ char vendor[32], model[32], type[32], revision[32];
+ int bus, channel, id, lun, number, i;
+ char line[256], dev_name[128];
+ const char *string;
+ FILE *proc_fp;
+ char *end;
+ struct
+ {
+ const char *name;
+ size_t name_len;
+ int is_int; /* integer valued? (not a string) */
+ union
+ {
+ void *v; /* avoids compiler warnings... */
+ char *str;
+ int *i;
+ }
+ u;
+ }
+ param[] =
+ {
+ {
+ "Vendor:", 7, 0,
+ {
+ 0}
+ }
+ ,
+ {
+ "Model:", 6, 0,
+ {
+ 0}
+ }
+ ,
+ {
+ "Type:", 5, 0,
+ {
+ 0}
+ }
+ ,
+ {
+ "Rev:", 4, 0,
+ {
+ 0}
+ }
+ ,
+ {
+ "scsi", 4, 1,
+ {
+ 0}
+ }
+ ,
+ {
+ "Channel:", 8, 1,
+ {
+ 0}
+ }
+ ,
+ {
+ "Id:", 3, 1,
+ {
+ 0}
+ }
+ ,
+ {
+ "Lun:", 4, 1,
+ {
+ 0}
+ }
+ };
+
+ param[0].u.str = vendor;
+ param[1].u.str = model;
+ param[2].u.str = type;
+ param[3].u.str = revision;
+ param[4].u.i = &bus;
+ param[5].u.i = &channel;
+ param[6].u.i = &id;
+ param[7].u.i = &lun;
+
+ DBG_INIT ();
+
+ open_aspi (); /* open aspi manager if not already done */
+
+ DBG (2, "find_devices: open temporary file '%s'\n", tmpAspi);
+ proc_fp = fopen (tmpAspi, "r");
+ if (!proc_fp)
+ {
+ DBG (1, "could not open %s for reading\n", tmpAspi);
+ return;
+ }
+
+ number = bus = channel = id = lun = -1;
+
+ vendor[0] = model[0] = type[0] = '\0';
+ if (findvendor)
+ findvendor_len = strlen (findvendor);
+ if (findmodel)
+ findmodel_len = strlen (findmodel);
+ if (findtype)
+ findtype_len = strlen (findtype);
+
+ while (!feof (proc_fp))
+ {
+ if (!fgets (line, sizeof (line), proc_fp))
+ break; /* at eof exit */
+
+ string = sanei_config_skip_whitespace (line);
+
+ while (*string)
+ {
+ for (i = 0; i < NELEMS (param); ++i)
+ {
+ if (strncmp (string, param[i].name, param[i].name_len) == 0)
+ {
+ string += param[i].name_len;
+ string = sanei_config_skip_whitespace (string);
+ if (param[i].is_int)
+ {
+ *param[i].u.i = strtol (string, &end, 10);
+ string = (char *) end;
+ }
+ else
+ {
+ strncpy (param[i].u.str, string, 32);
+ param[i].u.str[31] = '\0';
+ while (*string && !isspace ((int) *string))
+ ++string;
+ }
+ string = sanei_config_skip_whitespace (string);
+
+ if (param[i].u.v == &bus)
+ ++number;
+ break;
+ }
+ }
+ if (i >= NELEMS (param))
+ ++string; /* no match */
+ }
+
+ if ((findvendor && !vendor[0]) || (findmodel && !model[0])
+ || (findtype && !type[0])
+ || (findbus >= 0 && bus == -1) || (findchannel >= 0
+ && channel == -1)
+ || (findlun >= 0 && lun == -1))
+ /* some info is still missing */
+ continue;
+
+ if ((!findvendor || strncmp (vendor, findvendor, findvendor_len) == 0)
+ && (!findmodel || strncmp (model, findmodel, findmodel_len) == 0)
+ && (!findtype || strncmp (type, findtype, findtype_len) == 0)
+ && (findbus == -1 || bus == findbus)
+ && (findchannel == -1 || channel == findchannel)
+ && (findid == -1 || id == findid)
+ && (findlun == -1 || lun == findlun)
+ && get_devicename (bus, id, lun, dev_name, sizeof (dev_name)) >= 0
+ && (*attach) (dev_name) != SANE_STATUS_GOOD)
+ return;
+
+ vendor[0] = model[0] = type[0] = 0;
+ bus = channel = id = lun = -1;
+ }
+
+ DBG (2, "find_devices: close temporary file '%s'\n", tmpAspi);
+ fclose (proc_fp);
+
+ close_aspi (); /* close aspi manager */
+ }
+
+/* XXX untested code! */
+ SANE_Status
+ sanei_scsi_cmd2 (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+ ULONG rc; /* Returns. */
+ unsigned long cbreturn;
+ unsigned long cbParam;
+ if (aspi_buf == NULL) /* avoid SIGSEGV in memcpy() when calling
+ sanei_scsi_cmd2() while aspi-driver is closed */
+ {
+ DBG (1, "sanei_scsi_cmd: Error no device (aspi_buf == NULL)\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ if (PSRBlock == NULL) /* avoid SIGSEGV in memcpy() when calling
+ sanei_scsi_cmd2() while aspi-driver is closed */
+ {
+ DBG (1, "sanei_scsi_cmd: Error no device (PSRBlock == NULL)\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ memset (PSRBlock, 0, sizeof (SRB)); /* Okay, I'm paranoid. */
+ PSRBlock->cmd = SRB_Command; /* execute SCSI cmd */
+ PSRBlock->ha_num = fd_info[fd].bus; /* host adapter number */
+ PSRBlock->u.cmd.target = fd_info[fd].target; /* Target SCSI ID */
+ PSRBlock->u.cmd.lun = fd_info[fd].lun; /* Target SCSI LUN */
+ PSRBlock->flags = SRB_Post; /* posting enabled */
+ if (dst_size && *dst_size)
+ {
+ /* Reading. */
+ assert (*dst_size <= (size_t) sanei_scsi_max_request_size);
+ PSRBlock->u.cmd.data_len = *dst_size;
+ DBG (1, "sanei_scsi_cmd: Reading PSRBlock->u.cmd.data_len= %lu\n",
+ PSRBlock->u.cmd.data_len);
+ PSRBlock->flags |= SRB_Read;
+ }
+ else
+ {
+ /* Writing. */
+ PSRBlock->u.cmd.data_len = src_size;
+ DBG (1, "sanei_scsi_cmd: Writing PSRBlock->u.cmd.data_len= %lu\n",
+ PSRBlock->u.cmd.data_len);
+ assert (PSRBlock->u.cmd.data_len <=
+ (unsigned long) sanei_scsi_max_request_size);
+ if (PSRBlock->u.cmd.data_len)
+ PSRBlock->flags |= SRB_Write;
+ else
+ PSRBlock->flags |= SRB_NoTransfer;
+ memcpy (aspi_buf, src, PSRBlock->u.cmd.data_len);
+ }
+ PSRBlock->u.cmd.sense_len = 32; /* length of sense buffer */
+ PSRBlock->u.cmd.data_ptr = NULL; /* pointer to data buffer already registered */
+ PSRBlock->u.cmd.link_ptr = NULL; /* pointer to next SRB */
+ PSRBlock->u.cmd.cdb_len = cmd_size; /* SCSI command length */
+ memcpy (PSRBlock->u.cmd.cdb_st, cmd, cmd_size);
+
+ /* Do the command. */
+ rc = DosDevIOCtl (driver_handle, 0x92, 0x02,
+ (void *) PSRBlock, sizeof (SRB), &cbParam,
+ (void *) PSRBlock, sizeof (SRB), &cbreturn);
+
+ if (rc)
+ {
+ DBG (1, "sanei_scsi_cmd: DosDevIOCtl failed. rc= %lu \n", rc);
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ /* Get sense data if available. */
+ if ((PSRBlock->status == SRB_Aborted || PSRBlock->status == SRB_Error) &&
+ PSRBlock->u.cmd.target_status == SRB_CheckStatus
+ && fd_info[fd].sense_handler != 0)
+ {
+ SANEI_SCSI_Sense_Handler s_handler = fd_info[fd].sense_handler;
+ return (*s_handler) (fd, &PSRBlock->u.cmd.cdb_st[cmd_size],
+ fd_info[fd].sense_handler_arg);
+ }
+ if (PSRBlock->status != SRB_Done ||
+ PSRBlock->u.cmd.ha_status != SRB_NoError ||
+ PSRBlock->u.cmd.target_status != SRB_NoStatus)
+ {
+ DBG (1, "sanei_scsi_cmd: command 0x%02x failed.\n"
+ "PSRBlock->status= 0x%02x\n"
+ "PSRBlock->u.chm.ha_status= 0x%02x\n"
+ "PSRBlock->u.cmd.target_status= 0x%02x\n",
+ PSRBlock->u.cmd.cdb_st[0],
+ PSRBlock->status,
+ PSRBlock->u.cmd.ha_status, PSRBlock->u.cmd.target_status);
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ if (dst_size && *dst_size) /* Reading? */
+ memcpy ((char *) dst, aspi_buf, *dst_size);
+ return SANE_STATUS_GOOD;
+ }
+#endif /* USE == OS2_INTERFACE */
+
+#if USE == STUBBED_INTERFACE
+ SANE_Status
+ sanei_scsi_cmd2 (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+ return SANE_STATUS_UNSUPPORTED;
+ }
+#endif /* USE == STUBBED_INTERFACE */
+
+#if USE == IRIX_INTERFACE
+
+#define WE_HAVE_FIND_DEVICES
+
+ SANE_Status
+ sanei_scsi_cmd2 (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+ dsreq_t scsi_req; /* SCSI request */
+/* xxx obsolete size_t cdb_size; *//* Size of SCSI command */
+ static u_char *cmdbuf = NULL, /* Command buffer */
+ *sensebuf = NULL, /* Request sense buffer */
+ *databuf = NULL; /* Data buffer */
+
+ /*
+ * Allocate the sense and command data buffers as necessary; we have
+ * to do this to avoid buffer alignment problems, since some
+ * hardware requires these buffers to be 32-bit aligned.
+ */
+ if (cmdbuf == NULL)
+ {
+ cmdbuf = malloc (64);
+ sensebuf = malloc (1024); /* may be can reduced to 128 */
+ databuf = malloc (MAX_DATA);
+
+ if (cmdbuf == NULL || sensebuf == NULL || databuf == NULL)
+ return SANE_STATUS_NO_MEM;
+ }
+
+ /*
+ * Build the SCSI request...
+ */
+ /* xxx obsolete cdb_size = CDB_SIZE (*(u_char *) src);
+ */
+
+ DBG (1, "sanei_scsi_cmd: cmd_size = %d\n", cmd_size);
+
+ if (dst != NULL)
+ {
+ /*
+ * SCSI command returning/reading data...
+ */
+ scsi_req.ds_flags = DSRQ_READ | DSRQ_SENSE;
+ scsi_req.ds_time = 120 * 1000;
+ scsi_req.ds_cmdbuf = (caddr_t) cmdbuf;
+ scsi_req.ds_cmdlen = cmd_size;
+ scsi_req.ds_databuf = (caddr_t) databuf;
+ scsi_req.ds_datalen = *dst_size;
+ scsi_req.ds_sensebuf = (caddr_t) sensebuf;
+ scsi_req.ds_senselen = 128; /* 1024 does not work, 128 is tested (O.Rauch) */
+
+ /*
+ * Copy command to cmdbuf to assure 32-bit alignment.
+ */
+ memcpy (cmdbuf, cmd, cmd_size);
+ }
+ else
+ {
+ /*
+ * SCSI command sending/writing data...
+ */
+ scsi_req.ds_flags = DSRQ_WRITE | DSRQ_SENSE;
+ scsi_req.ds_time = 120 * 1000;
+ scsi_req.ds_cmdbuf = (caddr_t) cmdbuf;
+ scsi_req.ds_cmdlen = cmd_size;
+ scsi_req.ds_databuf = (caddr_t) databuf;
+ scsi_req.ds_datalen = src_size;
+ scsi_req.ds_sensebuf = (caddr_t) sensebuf;
+ scsi_req.ds_senselen = 128;
+
+ /*
+ * Copy command and data to local buffers to ensure 32-bit alignment...
+ */
+ memcpy (cmdbuf, (u_char *) cmd, cmd_size);
+ memcpy (databuf, (u_char *) src, src_size);
+ }
+
+ bzero (sensebuf, 128);
+
+ /*
+ * Do SCSI request...
+ */
+ if (ioctl (fd, DS_ENTER, &scsi_req) < 0)
+ {
+ DBG (1, "sanei_scsi_cmd: ioctl failed - %s\n", strerror (errno));
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ DBG (1, "sanei_scsi_cmd: status = %d\n", scsi_req.ds_status);
+
+ /*
+ * Set the incoming data size and copy the destination data as needed...
+ */
+ if (dst != NULL)
+ {
+ *dst_size = scsi_req.ds_datasent;
+
+ DBG (1, "sanei_scsi_cmd: read %d bytes\n", scsi_req.ds_datasent);
+
+ if (scsi_req.ds_datasent > 0)
+ memcpy (dst, databuf, scsi_req.ds_datasent);
+ }
+
+ /*
+ * Return the appropriate status code...
+ */
+ if (scsi_req.ds_status != 0)
+ {
+ if (scsi_req.ds_status == STA_BUSY)
+ return SANE_STATUS_DEVICE_BUSY;
+ else if (fd_info[fd].sense_handler)
+ return (*fd_info[fd].sense_handler) (fd, sensebuf,
+ fd_info[fd].sense_handler_arg);
+ else
+ return SANE_STATUS_IO_ERROR;
+ }
+ return SANE_STATUS_GOOD;
+ }
+
+ void
+ sanei_scsi_find_devices (const char *findvendor, const char *findmodel,
+ const char *findtype,
+ int findbus, int findchannel, int findid,
+ int findlun,
+ SANE_Status (*attach) (const char *dev))
+ {
+ size_t findvendor_len = 0, findmodel_len = 0;
+ /* Lengths of search strings */
+ inventory_t *inv; /* Current hardware inventory entry */
+ int bus, id, lun; /* Current Bus, ID, and LUN */
+ char dev_name[128]; /* SCSI device name */
+ int fd; /* SCSI file */
+ size_t inqsize; /* Size of returned inquiry data */
+ char vendor[9], /* Vendor name */
+ model[17]; /* Model/product name */
+ u_char inqdata[128], /* Inquiry data buffer */
+ inqcommand[6]; /* Inquiry command (0x12) buffer */
+
+ DBG_INIT ();
+
+ vendor[0] = model[0] = '\0';
+ if (findvendor)
+ findvendor_len = strlen (findvendor);
+ if (findmodel)
+ findmodel_len = strlen (findmodel);
+
+ if (findvendor != NULL)
+ DBG (1, "sanei_scsi_find_devices: looking for vendors starting "
+ "with \"%s\".\n", findvendor);
+
+ if (findmodel != NULL)
+ DBG (1, "sanei_scsi_find_devices: looking for models starting "
+ "with \"%s\".\n", findmodel);
+
+ setinvent ();
+
+ while ((inv = getinvent ()) != NULL)
+ {
+ if (inv->inv_class != INV_SCSI ||
+ (inv->inv_type != INV_SCANNER && inv->inv_type != INV_CPU))
+ continue;
+
+ bus = inv->inv_controller;
+ id = inv->inv_unit;
+ lun = inv->inv_state >> 8;
+
+ DBG (1, "sanei_scsi_find_devices: found %s on controller %d, "
+ "ID %d, LUN %d.\n",
+ inv->inv_type == INV_SCANNER ? "scanner" : "processor",
+ bus, id, lun);
+
+ if ((findbus >= 0 && bus != findbus) ||
+ (findid >= 0 && id != findid) || (findlun >= 0 && lun != findlun))
+ {
+ DBG (1, "sanei_scsi_find_devices: ignoring this device.\n");
+ continue;
+ }
+
+ sprintf (dev_name, "/dev/scsi/sc%dd%dl%d", bus, id, lun);
+ DBG (1, "sanei_scsi_find_devices: device name is \"%s\".\n",
+ dev_name);
+
+ /*
+ * Open the SCSI device and get the inquiry data...
+ */
+
+ if (sanei_scsi_open (dev_name, &fd, NULL, NULL) != SANE_STATUS_GOOD)
+ {
+ DBG (1,
+ "sanei_scsi_find_devices: unable to open device file - %s.\n",
+ strerror (errno));
+ continue;
+ }
+
+ DBG (1, "sanei_scsi_find_devices: device fd = %d.\n", fd);
+
+ inqsize = sizeof (inqdata);
+
+ inqcommand[0] = 0x12;
+ inqcommand[1] = 0;
+ inqcommand[2] = 0;
+ inqcommand[3] = sizeof (inqdata) >> 8;
+ inqcommand[4] = sizeof (inqdata);
+ inqcommand[5] = 0;
+
+ if (sanei_scsi_cmd (fd, inqcommand, sizeof (inqcommand), inqdata,
+ &inqsize) != SANE_STATUS_GOOD)
+ {
+ DBG (1,
+ "sanei_scsi_find_devices: unable to get inquiry data - %s.\n",
+ strerror (errno));
+ continue;
+ }
+
+ sanei_scsi_close (fd);
+
+ strncpy (vendor, (char *) inqdata + 8, 8);
+ vendor[8] = '\0';
+ strncpy (model, (char *) inqdata + 16, 16);
+ model[16] = '\0';
+
+ DBG (1, "sanei_scsi_find_devices: vendor = \'%s\', model = \'%s'.\n",
+ vendor, model);
+
+ /*
+ * Compare as necessary...
+ */
+
+ if ((findvendor != NULL
+ && strncmp (findvendor, vendor, findvendor_len))
+ || (findmodel != NULL
+ && strncmp (findmodel, model, findmodel_len)))
+ {
+ DBG (1, "sanei_scsi_find_devices: ignoring this device.\n");
+ continue;
+ }
+
+ /*
+ * OK, this one matches, so use it!
+ */
+
+ DBG (1, "sanei_scsi_find_devices: attaching this device.\n");
+
+ (*attach) (dev_name);
+ }
+ }
+#endif /* USE == IRIX_INTERFACE */
+
+#if USE == AIX_GSC_INTERFACE
+ SANE_Status
+ sanei_scsi_cmd2 (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+ scmd_t scmd;
+ /* xxx obsolete size_t cdb_size;
+ */
+ char sense_buf[32];
+ char status;
+
+ /* xxx obsolete cdb_size = CDB_SIZE (*(u_char *) src);
+ */
+
+ memset (&scmd, 0, sizeof (scmd));
+ if (dst_size && *dst_size)
+ {
+ /* xxx obsolete assert (cdb_size == src_size);
+ */
+ scmd.rw = 1;
+ scmd.data_buf = dst;
+ scmd.datalen = *dst_size;
+ }
+ else
+ {
+ /* assert (cdb_size <= src_size);
+ */
+ scmd.data_buf = (char *) src;
+ scmd.datalen = src_size;
+ }
+ scmd.cdb = (char *) cmd;
+ scmd.cdblen = cmd_size;
+ scmd.timeval = sane_scsicmd_timeout;
+ scmd.sense_buf = sense_buf;
+ scmd.senselen = sizeof (sense_buf);
+ scmd.statusp = &status;
+ DBG (1, "sanei_scsi_cmd: scmd.rw = %d, scmd.cdblen = %d, ",
+ scmd.rw, scmd.cdblen);
+ DBG (1, "scmd.cdb = {0x%x,0x%x,0x%x,0x%x,0x%x,0x%x, ...}\n",
+ scmd.cdb[0], scmd.cdb[1], scmd.cdb[2],
+ scmd.cdb[3], scmd.cdb[4], scmd.cdb[5]);
+ if (ioctl (fd, GSC_CMD, &scmd) < 0)
+ {
+ DBG (1, "sanei_scsi_cmd: ioctl(SIOC_IO) failed: %s\n",
+ strerror (errno));
+ return SANE_STATUS_IO_ERROR;
+ }
+ if (*scmd.statusp)
+ DBG (1, "sanei_scsi_cmd: SCSI completed with status=%d\n",
+ *scmd.statusp);
+
+ DBG (1, "sanei_scsi_cmd: dst = {0x%x,0x%x,0x%x,0x%x,0x%x,0x%x, ...}\n",
+ *((char *) dst + 0), *((char *) dst + 1), *((char *) dst + 2),
+ *((char *) dst + 3), *((char *) dst + 4), *((char *) dst + 5));
+
+ if (dst_size)
+ *dst_size = scmd.datalen;
+
+ if (scmd.senselen > 0
+ && (scmd.sense_buf[0] & 0x80) && fd_info[fd].sense_handler)
+ return (*fd_info[fd].sense_handler) (fd, (u_char *) scmd.sense_buf,
+ fd_info[fd].sense_handler_arg);
+ return SANE_STATUS_GOOD;
+ }
+#endif /* USE == AIX_GSC_INTERFACE */
+
+#if USE == SOLARIS_SG_INTERFACE
+
+#ifndef CCS_SENSE_LEN
+# define CCS_SENSE_LEN 18
+#endif
+
+ SANE_Status
+ sanei_scsi_cmd2 (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+ struct user_scsi us;
+ /* xxx obsolete size_t cdb_size;
+ */
+ char sensebf[CCS_SENSE_LEN];
+
+ /* xxx obsolete cdb_size = CDB_SIZE (*(u_char *) src);
+ */
+
+ /* first put the user scsi structure together. */
+ memset (&us, 0, sizeof (us));
+ us.us_cdbp = (caddr_t) cmd;
+ us.us_cdblen = cmd_size;
+ us.us_sensep = sensebf;
+ us.us_senselen = CCS_SENSE_LEN;
+ if (dst && dst_size && *dst_size)
+ {
+ us.us_bufp = (caddr_t) dst;
+ us.us_buflen = *dst_size;
+ us.us_flags = USER_SCSI_READ;
+ }
+ else
+ {
+ us.us_bufp = (caddr_t) src;
+ us.us_buflen = src_size;
+ us.us_flags = USER_SCSI_WRITE;
+ }
+ /* now run it */
+ if (ioctl (fd, USER_SCSI, &us) < 0)
+ return SANE_STATUS_IO_ERROR;
+ if (dst_size)
+ *dst_size -= us.us_resid;
+
+ return SANE_STATUS_GOOD;
+ }
+#endif /* USE == SOLARIS_SG_INTERFACE */
+
+#if USE == SOLARIS_INTERFACE
+
+#ifndef SC_NOT_READ
+# define SC_NOT_READY 0x02
+#endif
+
+#ifndef SC_BUSY
+# define SC_BUSY 0x08
+#endif
+#define DEF_TIMEOUT sane_scsicmd_timeout;
+
+/* Choosing one of the following DEF_SCG_FLG's SCG_DISRE_ENA allows
+ the SCSI driver to disconnect/reconnect. SCG_CMD_RETRY allows a
+ retry if a retryable error occurs.
+
+ Disallowing SCG_DISRE_ENA slows down the operation of the SCSI bus
+ while the scanner is working. If you have severe problems try to
+ set it to 0.
+
+ SCG_CMD_RETRY allows the driver to retry some commands. It should
+ normally be set. For some kinds of odd problems, it may cause the
+ machine to hang for some time. */
+
+#define DEF_SCG_FLG SCG_DISRE_ENA
+/* #define DEF_SCG_FLG 0 */
+/* #define DEF_SCG_FLG SCG_DISRE_ENA | SCG_CMD_RETRY */
+/* #define DEF_SCG_FLG SCG_CMD_RETRY */
+
+ static int d_errs = 100;
+
+ static SANE_Status
+ scsi_cmd (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size, int probing)
+ {
+ struct scg_cmd scmd;
+ /* xxx obsolete size_t cdb_size;
+ */
+ SANEI_SCSI_Sense_Handler handler;
+
+ /* xxx obsolete cdb_size = CDB_SIZE (*(u_char *) src);
+ */
+
+ memset (&scmd, 0, sizeof (scmd));
+ scmd.flags = DEF_SCG_FLG | (probing ? SCG_SILENT : 0);
+ if (dst && dst_size && *dst_size)
+ {
+ /* xxx obsolete assert (cdb_size == src_size);
+ */
+ scmd.flags |= SCG_RECV_DATA;
+ scmd.addr = dst;
+ scmd.size = *dst_size;
+ }
+ else
+ {
+ /* xxx obsolete assert (cdb_size <= src_size);
+ */
+ scmd.addr = (caddr_t) src;
+ scmd.size = src_size;
+ }
+ scmd.cdb_len = cmd_size;
+ scmd.sense_len = CCS_SENSE_LEN;
+ scmd.target = fd_info[fd].target;
+ /* use 2 second timeout when probing, 60 seconds otherwise: */
+ scmd.timeout = probing ? 2 : DEF_TIMEOUT;
+ memcpy (&scmd.cdb.g0_cdb.cmd, cmd, cmd_size);
+ scmd.cdb.cmd_cdb[1] |= fd_info[fd].lun << 5;
+ if (ioctl (fd, SCGIO_CMD, &scmd) < 0)
+ return SANE_STATUS_IO_ERROR;
+ if (dst_size)
+ *dst_size = scmd.size - scmd.resid;
+ if (scmd.error == 0 && scmd.errno == 0 && *(u_char *) & scmd.scb == 0)
+ return SANE_STATUS_GOOD;
+
+ if (scmd.error == SCG_TIMEOUT)
+ DBG (0, "sanei_scsi_cmd %x: timeout\n", scmd.cdb.g0_cdb.cmd);
+ else if (probing)
+ {
+ struct scsi_ext_sense *ext_sense =
+ (struct scsi_ext_sense *) &scmd.sense;
+
+ if (scmd.error < SCG_FATAL
+ && ((scmd.sense.code < 0x70 && scmd.sense.code != 0x04)
+ || (scmd.sense.code >= 0x70
+ && ext_sense->key != SC_NOT_READY)))
+ return SANE_STATUS_GOOD;
+ }
+ else
+ {
+ char errbf[128];
+ int i, rv, lifes;
+
+ handler = fd_info[fd].sense_handler;
+ DBG (3, "cmd=%x, error=%d:%s, bsiz=%d, stat=%x,%x,%x, slen=%d\n",
+ scmd.cdb.g0_cdb.cmd, scmd.error, strerror (scmd.errno),
+ ((dst_size != NULL) ? (*dst_size) : 0), scmd.u_scb.cmd_scb[0],
+ scmd.u_scb.cmd_scb[1], scmd.u_scb.cmd_scb[2], scmd.sense_count);
+ *errbf = '\0';
+ for (i = 0; i < scmd.sense_count; i++)
+ sprintf (errbf + strlen (errbf), "%x,", scmd.u_sense.cmd_sense[i]);
+ DBG (3, "sense=%s\n", errbf);
+
+ /* test_unit_ready on a busy unit returns error = 0 or 2 with
+ errno=EIO. I've seen 0 on a CDrom without a CD, and 2 on a
+ scanner just busy.
+
+ If (SANE_DEBUG_SANEI_SCSI > 100) lifes =
+ SANE_DEBUG_SANEI_SCSI - 100 use up one life for every
+ scmd.error abort and dump core when no lifes left
+ test_unit_ready commands are not counted. */
+ if (scmd.error)
+ {
+ if (sanei_debug_sanei_scsi > 100 &&
+ scmd.cdb.g0_cdb.cmd != SC_TEST_UNIT_READY)
+ {
+ lifes = sanei_debug_sanei_scsi - ++d_errs;
+ DBG (1, "sanei_scsi_cmd: %d lifes left\n", lifes);
+ assert (lifes > 0);
+ }
+ return SANE_STATUS_IO_ERROR;
+ }
+ if (scmd.u_scb.cmd_scb[0] == SC_BUSY)
+ return SANE_STATUS_DEVICE_BUSY;
+ if (*(u_char *) & scmd.sense && handler)
+ {
+ rv = (*handler) (fd, scmd.u_sense.cmd_sense,
+ fd_info[fd].sense_handler_arg);
+ DBG (2, "sanei_scsi_cmd: sense-handler returns %d\n", rv);
+ return rv;
+ }
+ }
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ SANE_Status
+ sanei_scsi_cmd2 (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+ return scsi_cmd (fd, cmd, cmd_size, src, src_size, dst, dst_size, 0);
+ }
+
+ static int unit_ready (int fd)
+ {
+ static const u_char test_unit_ready[] = { 0, 0, 0, 0, 0, 0 };
+ int status;
+
+ status = scsi_cmd (fd, test_unit_ready, sizeof (test_unit_ready),
+ 0, 0, 0, 0, 1);
+ return (status == SANE_STATUS_GOOD);
+ }
+
+#endif /* USE == SOLARIS_INTERFACE */
+
+
+#if USE == SOLARIS_USCSI_INTERFACE
+
+#define DEF_TIMEOUT sane_scsicmd_timeout;
+
+ static int d_errs = 100;
+ typedef struct scsi_extended_sense extended_sense_t;
+ typedef struct scsi_inquiry scsi_inquiry_t;
+
+ static SANE_Status
+ scsi_cmd (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size, int probing)
+ {
+ struct uscsi_cmd us;
+ scsi_inquiry_t inquiry, *iq = &inquiry;
+ extended_sense_t sense, *sp = &sense;
+ SANEI_SCSI_Sense_Handler handler;
+
+ memset (&us, 0, sizeof (us));
+ memset (sp, 0, sizeof (*sp));
+
+ us.uscsi_flags = USCSI_SILENT | USCSI_RQENABLE | USCSI_DIAGNOSE;
+ us.uscsi_timeout = probing ? 2 : DEF_TIMEOUT;
+ us.uscsi_rqbuf = (caddr_t) sp; /* sense data address */
+ us.uscsi_rqlen = sizeof (extended_sense_t); /* length of sense data */
+
+ if (dst && dst_size && *dst_size)
+ {
+ us.uscsi_flags |= USCSI_READ;
+ us.uscsi_bufaddr = (caddr_t) dst;
+ us.uscsi_buflen = *dst_size;
+ }
+ else
+ {
+ us.uscsi_flags |= USCSI_WRITE;
+ us.uscsi_bufaddr = (caddr_t) src;
+ us.uscsi_buflen = src_size;
+ }
+
+ us.uscsi_cdblen = cmd_size;
+ us.uscsi_cdb = (caddr_t) cmd;
+
+ if (ioctl (fd, USCSICMD, &us) < 0)
+ return SANE_STATUS_IO_ERROR;
+
+ if (dst_size)
+ *dst_size = us.uscsi_buflen - us.uscsi_resid;
+
+ if ((us.uscsi_status & STATUS_MASK) == STATUS_GOOD)
+ return SANE_STATUS_GOOD;
+
+ if (sp->es_key == SUN_KEY_TIMEOUT)
+ DBG (0, "sanei_scsi_cmd %x: timeout\n", *(char *) cmd);
+ else
+ {
+ char errbf[128];
+ int i, rv, lifes;
+
+ handler = fd_info[fd].sense_handler;
+ DBG (3, "cmd=%x, scsi_status=%x\n", *(char *) cmd, us.uscsi_status);
+ *errbf = '\0';
+
+ for (i = 0; i < us.uscsi_rqlen; i++)
+ sprintf (errbf + strlen (errbf), "%x,", *(sp + i));
+
+ DBG (3, "sense=%s\n", errbf);
+
+#if 0
+ if (us.error)
+ {
+ if (sanei_debug_sanei_scsi > 100 &&
+ scmd.cdb.g0_cdb.cmd != SC_TEST_UNIT_READY)
+ {
+ lifes = sanei_debug_sanei_scsi - ++d_errs;
+ DBG (1, "sanei_scsi_cmd: %d lifes left\n", lifes);
+ assert (lifes > 0);
+ }
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ if (scmd.u_scb.cmd_scb[0] == SC_BUSY)
+ return SANE_STATUS_DEVICE_BUSY;
+#endif
+
+ if (handler)
+ {
+ rv = (*handler) (fd, (unsigned char *) sp,
+ fd_info[fd].sense_handler_arg);
+ DBG (2, "sanei_scsi_cmd: sense-handler returns %d\n", rv);
+ return rv;
+ }
+ }
+
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ SANE_Status
+ sanei_scsi_cmd2 (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+ return scsi_cmd (fd, cmd, cmd_size, src, src_size, dst, dst_size, 0);
+ }
+
+ static int unit_ready (int fd)
+ {
+ static const u_char test_unit_ready[] = { 0, 0, 0, 0, 0, 0 };
+ int status;
+
+ status = scsi_cmd (fd, test_unit_ready, sizeof (test_unit_ready),
+ 0, 0, 0, 0, 1);
+ return (status == SANE_STATUS_GOOD);
+ }
+#endif /* USE == SOLARIS_USCSI_INTERFACE */
+
+#if USE == WIN32_INTERFACE
+
+SANE_Status
+sanei_scsi_cmd2 (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+{
+ struct pkt {
+ SCSI_PASS_THROUGH_DIRECT sptd;
+ unsigned char sense[255];
+ } pkt;
+ DWORD BytesReturned;
+ BOOL ret;
+
+ memset(&pkt, 0, sizeof( pkt ));
+ pkt.sptd.Length = sizeof( SCSI_PASS_THROUGH_DIRECT );
+
+ pkt.sptd.PathId = fd_info[fd].bus;
+ pkt.sptd.TargetId = fd_info[fd].target;
+ pkt.sptd.Lun = fd_info[fd].lun;
+
+ assert(cmd_size == 6 || cmd_size == 10 || cmd_size == 12 || cmd_size == 16);
+ memcpy(pkt.sptd.Cdb, cmd, cmd_size);
+ pkt.sptd.CdbLength = cmd_size;
+
+ if (dst_size && *dst_size)
+ {
+ pkt.sptd.DataIn = SCSI_IOCTL_DATA_IN;
+ pkt.sptd.DataTransferLength = *dst_size;
+ pkt.sptd.DataBuffer = dst;
+ }
+ else if (src_size)
+ {
+ pkt.sptd.DataIn = SCSI_IOCTL_DATA_OUT;
+ pkt.sptd.DataTransferLength = src_size;
+ pkt.sptd.DataBuffer = src;
+ }
+ else {
+ pkt.sptd.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
+ }
+
+ pkt.sptd.TimeOutValue = sane_scsicmd_timeout;
+
+ pkt.sptd.SenseInfoOffset = offsetof(struct pkt, sense);
+ pkt.sptd.SenseInfoLength = sizeof(pkt.sense);
+
+ ret = DeviceIoControl(fd,
+ IOCTL_SCSI_PASS_THROUGH_DIRECT,
+ &pkt.sptd, sizeof( pkt ),
+ &pkt.sptd, sizeof( pkt ),
+ &BytesReturned, NULL );
+
+ if (ret == 0)
+ {
+ DBG (1, "sanei_scsi_cmd2: DeviceIoControl() failed: %ld\n",
+ GetLastError());
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ if (pkt.sptd.ScsiStatus == 2){
+ /* Check condition. */
+ SANEI_SCSI_Sense_Handler handler;
+
+ handler = fd_info[fd].sense_handler;
+ if (handler) {
+ return handler(fd, pkt.sense, fd_info[fd].sense_handler_arg);
+ }
+ else {
+ return SANE_STATUS_IO_ERROR;
+ }
+ }
+ else if (pkt.sptd.ScsiStatus != 0) {
+ DBG (1, "sanei_scsi_cmd2: ScsiStatus is %d\n",
+ pkt.sptd.ScsiStatus);
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ if (dst_size) {
+ *dst_size = pkt.sptd.DataTransferLength;
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+#define WE_HAVE_FIND_DEVICES
+
+/* This is almost the same algorithm used in sane-find-scanner. */
+void
+sanei_scsi_find_devices (const char *findvendor, const char *findmodel,
+ const char *findtype,
+ int findbus, int findchannel, int findid, int findlun,
+ SANE_Status (*attach) (const char *dev))
+{
+ int hca;
+ HANDLE fd;
+ char scsi_hca_name[20];
+ char buffer[4096];
+ DWORD BytesReturned;
+ BOOL ret;
+ PSCSI_ADAPTER_BUS_INFO adapter;
+ PSCSI_INQUIRY_DATA inquiry;
+ int i;
+
+ DBG_INIT();
+
+ hca = 0;
+
+ for(hca = 0; ; hca++) {
+
+ /* Open the adapter */
+ snprintf(scsi_hca_name, 20, "\\\\.\\Scsi%d:", hca);
+ fd = CreateFile(scsi_hca_name, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING,
+ FILE_FLAG_RANDOM_ACCESS, NULL );
+
+ if (fd == INVALID_HANDLE_VALUE) {
+ /* Assume there is no more adapter. This is wrong in the case
+ * of hot-plug stuff, but I have yet to see it on a user
+ * machine. */
+ break;
+ }
+
+ /* Get the inquiry info for the devices on that hca. */
+ ret = DeviceIoControl(fd,
+ IOCTL_SCSI_GET_INQUIRY_DATA,
+ NULL,
+ 0,
+ buffer,
+ sizeof(buffer),
+ &BytesReturned,
+ FALSE);
+
+ if(ret == 0)
+ {
+ CloseHandle(fd);
+ continue;
+ }
+
+ adapter = (PSCSI_ADAPTER_BUS_INFO)buffer;
+
+ for(i = 0; i < adapter->NumberOfBuses; i++) {
+
+ if (adapter->BusData[i].InquiryDataOffset == 0) {
+ /* No device here */
+ continue;
+ }
+
+ inquiry = (PSCSI_INQUIRY_DATA) (buffer +
+ adapter->BusData[i].InquiryDataOffset);
+
+ while(1) {
+
+ if ((findvendor == NULL || strncmp(findvendor, (char *)&inquiry->InquiryData[8], 8) == 0)) {
+ DBG(1, "OK1\n");
+ } else {
+ DBG(1, "failed for [%s] and [%s]\n",findvendor, (char *)&inquiry->InquiryData[8] );
+ }
+
+
+ /* Check if this device fits the criteria. */
+ if ((findvendor == NULL || strncmp(findvendor, (char *)&inquiry->InquiryData[8], strlen(findvendor)) == 0) &&
+ (findmodel == NULL || strncmp(findmodel, (char *)&inquiry->InquiryData[16], strlen(findmodel)) == 0) &&
+ (findbus == -1 || findbus == hca) &&
+ (findchannel == -1 || findchannel == inquiry->PathId) &&
+ (findid == -1 || findid == inquiry->TargetId) &&
+ (findlun == -1 || findlun == inquiry->Lun)) {
+
+ char device_name[20];
+ sprintf(device_name, "h%db%dt%dl%d", hca, inquiry->PathId, inquiry->TargetId, inquiry->Lun);
+ attach(device_name);
+ }
+ if (inquiry->NextInquiryDataOffset == 0) {
+ /* No device here */
+ break;
+ } else {
+ inquiry = (PSCSI_INQUIRY_DATA) (buffer +
+ inquiry->NextInquiryDataOffset);
+ }
+ }
+ }
+ CloseHandle(fd);
+
+ }
+}
+#endif /* USE == WIN32_INTERFACE */
+
+#if USE == MACOSX_INTERFACE
+
+# ifdef HAVE_IOKIT_CDB_IOSCSILIB_H
+
+ static SANE_Status
+ sanei_scsi_cmd2_old_api (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+ mach_port_t masterPort;
+ IOReturn ioReturnValue;
+ io_object_t scsiDevice;
+ int i;
+ CFMutableDictionaryRef scsiMatchDictionary;
+ int deviceTypeNumber;
+ CFNumberRef deviceTypeRef;
+ io_iterator_t scsiObjectIterator;
+ io_object_t device;
+ CFNumberRef IOUnitRef;
+ int iounit;
+ CFNumberRef scsiTargetRef;
+ int scsitarget;
+ CFNumberRef scsiLunRef;
+ int scsilun;
+ IOCFPlugInInterface **plugInInterface;
+ SInt32 score;
+ HRESULT plugInResult;
+ IOSCSIDeviceInterface **scsiDeviceInterface;
+ IOCDBCommandInterface **cdbCommandInterface;
+ CDBInfo cdb;
+ IOVirtualRange range;
+ UInt32 transferCount;
+ Boolean isWrite;
+ SCSIResults results;
+ UInt32 seqNumber;
+
+ masterPort = 0;
+ ioReturnValue = IOMasterPort (MACH_PORT_NULL, &masterPort);
+ if (ioReturnValue != kIOReturnSuccess || masterPort == 0)
+ {
+ DBG (5, "Could not get I/O master port (0x%08x)\n", ioReturnValue);
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ scsiDevice = 0;
+ for (i = 0; !scsiDevice && i < 2; i++)
+ {
+ scsiMatchDictionary = IOServiceMatching (kIOSCSIDeviceClassName);
+ if (scsiMatchDictionary == NULL)
+ {
+ DBG (5, "Could not create SCSI matching dictionary\n");
+ return SANE_STATUS_NO_MEM;
+ }
+
+ deviceTypeNumber =
+ (i == 0 ? kSCSIDevTypeScanner : kSCSIDevTypeProcessor);
+ deviceTypeRef = CFNumberCreate (NULL, kCFNumberIntType,
+ &deviceTypeNumber);
+ CFDictionarySetValue (scsiMatchDictionary,
+ CFSTR (kSCSIPropertyDeviceTypeID),
+ deviceTypeRef);
+ CFRelease (deviceTypeRef);
+
+ scsiObjectIterator = 0;
+ ioReturnValue = IOServiceGetMatchingServices (masterPort,
+ scsiMatchDictionary,
+ &scsiObjectIterator);
+ if (ioReturnValue != kIOReturnSuccess)
+ {
+ DBG (5, "Could not match services (0x%08x)\n", ioReturnValue);
+ return SANE_STATUS_NO_MEM;
+ }
+
+ while ((device = IOIteratorNext (scsiObjectIterator)))
+ {
+ IOUnitRef =
+ IORegistryEntryCreateCFProperty (device,
+ CFSTR (kSCSIPropertyIOUnit),
+ NULL, 0);
+ CFNumberGetValue (IOUnitRef, kCFNumberIntType, &iounit);
+ CFRelease (IOUnitRef);
+ scsiTargetRef =
+ IORegistryEntryCreateCFProperty (device,
+ CFSTR (kSCSIPropertyTarget),
+ NULL, 0);
+ CFNumberGetValue (scsiTargetRef, kCFNumberIntType, &scsitarget);
+ CFRelease (scsiTargetRef);
+ scsiLunRef =
+ IORegistryEntryCreateCFProperty (device,
+ CFSTR (kSCSIPropertyLun),
+ NULL, 0);
+ CFNumberGetValue (scsiLunRef, kCFNumberIntType, &scsilun);
+ CFRelease (scsiLunRef);
+
+ if (fd_info[fd].bus == iounit &&
+ fd_info[fd].target == scsitarget &&
+ fd_info[fd].lun == scsilun)
+ scsiDevice = device;
+ else
+ IOObjectRelease (device);
+ }
+ IOObjectRelease (scsiObjectIterator);
+ }
+ if (!scsiDevice)
+ {
+ DBG (5, "Device not found (unit %i, target %i, lun %i)\n",
+ fd_info[fd].bus, fd_info[fd].target, fd_info[fd].lun);
+ return SANE_STATUS_INVAL;
+ }
+
+ plugInInterface = NULL;
+ score = 0;
+ ioReturnValue = IOCreatePlugInInterfaceForService (scsiDevice,
+ kIOSCSIUserClientTypeID,
+ kIOCFPlugInInterfaceID,
+ &plugInInterface,
+ &score);
+ if (ioReturnValue != kIOReturnSuccess || plugInInterface == NULL)
+ {
+ DBG (5, "Error creating plugin interface (0x%08x)\n", ioReturnValue);
+ return SANE_STATUS_NO_MEM;
+ }
+
+ scsiDeviceInterface = NULL;
+ plugInResult = (*plugInInterface)->
+ QueryInterface (plugInInterface,
+ CFUUIDGetUUIDBytes (kIOSCSIDeviceInterfaceID),
+ (LPVOID) & scsiDeviceInterface);
+ if (plugInResult != S_OK || scsiDeviceInterface == NULL)
+ {
+ DBG (5, "Couldn't create SCSI device interface (%ld)\n", plugInResult);
+ return SANE_STATUS_NO_MEM;
+ }
+
+ (*plugInInterface)->Release (plugInInterface);
+ IOObjectRelease (scsiDevice);
+
+ ioReturnValue = (*scsiDeviceInterface)->open (scsiDeviceInterface);
+ if (ioReturnValue != kIOReturnSuccess)
+ {
+ DBG (5, "Error opening SCSI interface (0x%08x)\n", ioReturnValue);
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ cdbCommandInterface = NULL;
+ plugInResult = (*scsiDeviceInterface)->
+ QueryInterface (scsiDeviceInterface,
+ CFUUIDGetUUIDBytes (kIOCDBCommandInterfaceID),
+ (LPVOID) & cdbCommandInterface);
+ if (plugInResult != S_OK || cdbCommandInterface == NULL)
+ {
+ DBG (5, "Error creating CDB interface (%ld)\n", plugInResult);
+ return SANE_STATUS_NO_MEM;
+ }
+
+ cdb.cdbLength = cmd_size;
+ memcpy (&cdb.cdb, cmd, cmd_size);
+ if (dst && dst_size)
+ {
+ bzero (dst, *dst_size);
+ range.address = (IOVirtualAddress) dst;
+ range.length = *dst_size;
+ transferCount = *dst_size;
+ isWrite = false;
+ }
+ else
+ {
+ range.address = (IOVirtualAddress) src;
+ range.length = src_size;
+ transferCount = src_size;
+ isWrite = true;
+ }
+
+ seqNumber = 0;
+ ioReturnValue = (*cdbCommandInterface)->
+ setAndExecuteCommand (cdbCommandInterface, &cdb, transferCount,
+ &range, 1, isWrite, sane_scsicmd_timeout * 1000,
+ 0, 0, 0, &seqNumber);
+ if (ioReturnValue != kIOReturnSuccess &&
+ ioReturnValue != kIOReturnUnderrun)
+ {
+ DBG (5, "Error executing CDB command (0x%08x)\n", ioReturnValue);
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ ioReturnValue = (*cdbCommandInterface)->getResults (cdbCommandInterface,
+ &results);
+ if (ioReturnValue != kIOReturnSuccess &&
+ ioReturnValue != kIOReturnUnderrun)
+ {
+ DBG (5, "Error getting results from CDB Interface (0x%08x)\n",
+ ioReturnValue);
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ if (dst && dst_size)
+ *dst_size = results.bytesTransferred;
+
+ (*cdbCommandInterface)->Release (cdbCommandInterface);
+ (*scsiDeviceInterface)->close (scsiDeviceInterface);
+ (*scsiDeviceInterface)->Release (scsiDeviceInterface);
+
+ return SANE_STATUS_GOOD;
+ }
+
+
+ static void
+ sanei_scsi_find_devices_old_api (const char *findvendor,
+ const char *findmodel,
+ const char *findtype, int findbus,
+ int findchannel, int findid, int findlun,
+ SANE_Status (*attach) (const char *dev))
+ {
+ mach_port_t masterPort;
+ IOReturn ioReturnValue;
+ int i;
+ CFMutableDictionaryRef scsiMatchDictionary;
+ int deviceTypeNumber;
+ CFNumberRef deviceTypeRef;
+ io_iterator_t scsiObjectIterator;
+ io_object_t scsiDevice;
+ CFNumberRef IOUnitRef;
+ int iounit;
+ CFNumberRef scsiTargetRef;
+ int scsitarget;
+ CFNumberRef scsiLunRef;
+ int scsilun;
+ IOCFPlugInInterface **plugInInterface;
+ SInt32 score;
+ HRESULT plugInResult;
+ IOSCSIDeviceInterface **scsiDeviceInterface;
+ SCSIInquiry inquiry;
+ UInt32 inquirySize;
+ char devname[16];
+
+ masterPort = 0;
+ ioReturnValue = IOMasterPort (MACH_PORT_NULL, &masterPort);
+ if (ioReturnValue != kIOReturnSuccess || masterPort == 0)
+ {
+ DBG (5, "Could not get I/O master port (0x%08x)\n", ioReturnValue);
+ return;
+ }
+
+ for (i = 0; i < 2; i++)
+ {
+ scsiMatchDictionary = IOServiceMatching (kIOSCSIDeviceClassName);
+ if (scsiMatchDictionary == NULL)
+ {
+ DBG (5, "Could not create SCSI matching dictionary\n");
+ return;
+ }
+ deviceTypeNumber =
+ (i == 0 ? kSCSIDevTypeScanner : kSCSIDevTypeProcessor);
+ deviceTypeRef = CFNumberCreate (NULL, kCFNumberIntType,
+ &deviceTypeNumber);
+ CFDictionarySetValue (scsiMatchDictionary,
+ CFSTR (kSCSIPropertyDeviceTypeID),
+ deviceTypeRef);
+ CFRelease (deviceTypeRef);
+
+ scsiObjectIterator = 0;
+ ioReturnValue = IOServiceGetMatchingServices (masterPort,
+ scsiMatchDictionary,
+ &scsiObjectIterator);
+ if (ioReturnValue != kIOReturnSuccess)
+ {
+ DBG (5, "Could not match services (0x%08x)\n", ioReturnValue);
+ return;
+ }
+
+ while ((scsiDevice = IOIteratorNext (scsiObjectIterator)))
+ {
+ IOUnitRef =
+ IORegistryEntryCreateCFProperty (scsiDevice,
+ CFSTR (kSCSIPropertyIOUnit),
+ NULL, 0);
+ CFNumberGetValue (IOUnitRef, kCFNumberIntType, &iounit);
+ CFRelease (IOUnitRef);
+ scsiTargetRef =
+ IORegistryEntryCreateCFProperty (scsiDevice,
+ CFSTR (kSCSIPropertyTarget),
+ NULL, 0);
+ CFNumberGetValue (scsiTargetRef, kCFNumberIntType, &scsitarget);
+ CFRelease (scsiTargetRef);
+ scsiLunRef =
+ IORegistryEntryCreateCFProperty (scsiDevice,
+ CFSTR (kSCSIPropertyLun),
+ NULL, 0);
+ CFNumberGetValue (scsiLunRef, kCFNumberIntType, &scsilun);
+ CFRelease (scsiLunRef);
+
+ plugInInterface = NULL;
+ score = 0;
+ ioReturnValue =
+ IOCreatePlugInInterfaceForService (scsiDevice,
+ kIOSCSIUserClientTypeID,
+ kIOCFPlugInInterfaceID,
+ &plugInInterface, &score);
+ if (ioReturnValue != kIOReturnSuccess || plugInInterface == NULL)
+ {
+ DBG (5, "Error creating plugin interface (0x%08x)\n",
+ ioReturnValue);
+ return;
+ }
+
+ scsiDeviceInterface = NULL;
+ plugInResult = (*plugInInterface)->
+ QueryInterface (plugInInterface,
+ CFUUIDGetUUIDBytes (kIOSCSIDeviceInterfaceID),
+ (LPVOID) & scsiDeviceInterface);
+ if (plugInResult != S_OK || scsiDeviceInterface == NULL)
+ {
+ DBG (5, "Couldn't create SCSI device interface (%ld)\n",
+ plugInResult);
+ return;
+ }
+
+ (*plugInInterface)->Release (plugInInterface);
+ IOObjectRelease (scsiDevice);
+
+ ioReturnValue = (*scsiDeviceInterface)->
+ getInquiryData (scsiDeviceInterface, &inquiry,
+ sizeof (SCSIInquiry), &inquirySize);
+
+ (*scsiDeviceInterface)->Release (scsiDeviceInterface);
+
+ if ((findlun < 0 || findlun == scsilun) &&
+ (findvendor == NULL || strncmp (findvendor,
+ inquiry.vendorName,
+ strlen (findvendor)) == 0) &&
+ (findmodel == NULL || strncmp (findmodel,
+ inquiry.productName,
+ strlen (findmodel)) == 0))
+ {
+ sprintf (devname, "u%dt%dl%d", iounit, scsitarget, scsilun);
+ (*attach) (devname);
+ }
+ }
+ IOObjectRelease (scsiObjectIterator);
+ }
+ }
+
+# endif /* ifdef HAVE_IOKIT_CDB_IOSCSILIB_H */
+
+# if defined (HAVE_IOKIT_SCSI_SCSICOMMANDOPERATIONCODES_H) || \
+ defined (HAVE_IOKIT_SCSI_COMMANDS_SCSICOMMANDOPERATIONCODES_H)
+
+ static
+ void CreateMatchingDictionaryForSTUC (SInt32 peripheralDeviceType,
+ const char *findvendor,
+ const char *findmodel,
+ const CFDataRef scsiguid,
+ CFMutableDictionaryRef * matchingDict)
+ {
+ CFMutableDictionaryRef subDict;
+ CFNumberRef deviceTypeRef;
+ CFStringRef str;
+
+ /* Create the dictionaries */
+ *matchingDict =
+ CFDictionaryCreateMutable (kCFAllocatorDefault, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ if (*matchingDict == NULL)
+ {
+ return;
+ }
+
+ subDict =
+ CFDictionaryCreateMutable (kCFAllocatorDefault, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ if (subDict == NULL)
+ {
+ CFRelease (*matchingDict);
+ *matchingDict = NULL;
+ return;
+ }
+
+ /* Create a dictionary with the "SCSITaskDeviceCategory" key with the
+ appropriate value for the device type we're interested in.*/
+
+ CFDictionarySetValue (subDict,
+ CFSTR (kIOPropertySCSITaskDeviceCategory),
+ CFSTR (kIOPropertySCSITaskUserClientDevice));
+
+ deviceTypeRef = CFNumberCreate (kCFAllocatorDefault, kCFNumberIntType,
+ &peripheralDeviceType);
+ CFDictionarySetValue (subDict,
+ CFSTR (kIOPropertySCSIPeripheralDeviceType),
+ deviceTypeRef);
+ CFRelease (deviceTypeRef);
+
+ /* Add search for a vendor or model */
+
+ if (findvendor)
+ {
+ str = CFStringCreateWithCString (kCFAllocatorDefault, findvendor,
+ kCFStringEncodingUTF8);
+ CFDictionarySetValue (subDict,
+ CFSTR (kIOPropertySCSIVendorIdentification),
+ str);
+ CFRelease (str);
+ }
+ if (findmodel)
+ {
+ str = CFStringCreateWithCString (kCFAllocatorDefault, findmodel,
+ kCFStringEncodingUTF8);
+ CFDictionarySetValue (subDict,
+ CFSTR (kIOPropertySCSIProductIdentification),
+ str);
+ CFRelease (str);
+ }
+ if (scsiguid)
+ {
+ CFDictionarySetValue (subDict,
+ CFSTR
+ (kIOPropertySCSITaskUserClientInstanceGUID),
+ scsiguid);
+ }
+
+ /* Add the dictionary to the main dictionary with the key "IOPropertyMatch"
+ to narrow the search to the above dictionary. */
+
+ CFDictionarySetValue (*matchingDict, CFSTR (kIOPropertyMatchKey), subDict);
+ CFRelease (subDict);
+ }
+
+ static
+ void CreateDeviceInterfaceUsingSTUC (io_object_t scsiDevice,
+ IOCFPlugInInterface ***
+ thePlugInInterface,
+ SCSITaskDeviceInterface ***
+ theInterface)
+ {
+ IOReturn ioReturnValue;
+ IOCFPlugInInterface **plugInInterface = NULL;
+ SInt32 score = 0;
+ HRESULT plugInResult;
+ SCSITaskDeviceInterface **interface = NULL;
+
+ /* Create the base interface of type IOCFPlugInInterface.
+ This object will be used to create the SCSI device interface object. */
+
+ ioReturnValue =
+ IOCreatePlugInInterfaceForService (scsiDevice,
+ kIOSCSITaskDeviceUserClientTypeID,
+ kIOCFPlugInInterfaceID,
+ &plugInInterface, &score);
+ if (ioReturnValue != kIOReturnSuccess)
+ {
+ DBG (5, "Error creating plugin interface (0x%08x)\n", ioReturnValue);
+ return;
+ }
+
+ /* Query the base plugin interface for an instance of the specific
+ SCSI device interface object. */
+
+ plugInResult =
+ (*plugInInterface)->QueryInterface (plugInInterface,
+ CFUUIDGetUUIDBytes
+ (kIOSCSITaskDeviceInterfaceID),
+ (LPVOID) & interface);
+ if (plugInResult != S_OK)
+ {
+ DBG (5, "Couldn't create SCSI device interface (%ld)\n",
+ (long) plugInResult);
+ return;
+ }
+
+ /* Set the return values. */
+
+ *thePlugInInterface = plugInInterface;
+ *theInterface = interface;
+ }
+
+ static SANE_Status
+ ExecuteSCSITask (SCSITaskInterface ** task,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+ SCSITaskStatus taskStatus;
+ SCSI_Sense_Data senseData;
+ SCSICommandDescriptorBlock cdb;
+ IOReturn ioReturnValue;
+#ifdef HAVE_SCSITASKSGELEMENT
+ SCSITaskSGElement range;
+#else
+ IOVirtualRange range;
+#endif
+ UInt64 transferCount = 0;
+ UInt64 data_length = 0;
+ UInt8 transferType = 0;
+
+ if (dst && dst_size) /* isRead */
+ {
+ DBG (6, "isRead dst_size:%ld\n", *dst_size);
+
+ /* Zero the buffer. */
+ bzero (dst, *dst_size);
+
+ /* Configure the virtual range for the buffer. */
+ range.address = (long) dst;
+ range.length = *dst_size;
+
+ data_length = *dst_size;
+ transferType = kSCSIDataTransfer_FromTargetToInitiator;
+ }
+ else
+ {
+ DBG (6, "isWrite src_size:%ld\n", src_size);
+
+ /* Configure the virtual range for the buffer. */
+ range.address = (long) src;
+ range.length = src_size;
+
+ data_length = src_size;
+ transferType = kSCSIDataTransfer_FromInitiatorToTarget;
+ }
+
+
+ /* zero the senseData and CDB */
+ bzero (&senseData, sizeof (senseData));
+ bzero (cdb, sizeof (cdb));
+
+ /* copy the command data */
+ memcpy (cdb, cmd, cmd_size);
+
+ /* Set the actual cdb in the task */
+ ioReturnValue = (*task)->SetCommandDescriptorBlock (task, cdb, cmd_size);
+ if (ioReturnValue != kIOReturnSuccess)
+ {
+ DBG (5, "Error setting CDB (0x%08x)\n", ioReturnValue);
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ /* Set the scatter-gather entry in the task */
+ ioReturnValue = (*task)->SetScatterGatherEntries (task, &range, 1,
+ data_length,
+ transferType);
+ if (ioReturnValue != kIOReturnSuccess)
+ {
+ DBG (5, "Error setting scatter-gather entries (0x%08x)\n",
+ ioReturnValue);
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ /* Set the timeout in the task */
+ ioReturnValue = (*task)->SetTimeoutDuration (task,
+ sane_scsicmd_timeout * 1000);
+ if (ioReturnValue != kIOReturnSuccess)
+ {
+ DBG (5, "Error setting timeout (0x%08x)\n", ioReturnValue);
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ DBG (5, "Executing command\n");
+
+ /* Send it! */
+ ioReturnValue = (*task)->ExecuteTaskSync (task, &senseData, &taskStatus,
+ &transferCount);
+ if (ioReturnValue != kIOReturnSuccess)
+ {
+ DBG (5, "Error executing task (0x%08x)\n", ioReturnValue);
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ DBG (5, "ExecuteTaskSync OK Transferred %lld bytes\n", transferCount);
+
+ if (taskStatus != kSCSITaskStatus_GOOD)
+ {
+ DBG (5, "taskStatus = 0x%08x\n", taskStatus);
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ /* Task worked correctly */
+ if (dst && dst_size)
+ *dst_size = transferCount;
+
+ return SANE_STATUS_GOOD;
+ }
+
+ static SANE_Status
+ ExecuteCommandUsingSTUC (SCSITaskDeviceInterface ** interface,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+ SCSITaskInterface **task;
+ IOReturn ioReturnValue;
+ SANE_Status returnValue;
+
+ /* Get exclusive access for the device if we can. This must be done
+ before any SCSITasks can be created and sent to the device. */
+ ioReturnValue = (*interface)->ObtainExclusiveAccess (interface);
+
+ if (ioReturnValue != kIOReturnSuccess)
+ {
+ DBG (5, "ObtainExclusiveAccess failed (0x%08x)\n", ioReturnValue);
+ return SANE_STATUS_NO_MEM;
+ }
+
+ /* Create a task now that we have exclusive access */
+ task = (*interface)->CreateSCSITask (interface);
+
+ if (task == NULL)
+ {
+ DBG (5, "CreateSCSITask returned NULL\n");
+ (*interface)->ReleaseExclusiveAccess (interface);
+ return SANE_STATUS_NO_MEM;
+ }
+
+ returnValue = ExecuteSCSITask (task, cmd, cmd_size,
+ src, src_size, dst, dst_size);
+
+ /* Release the task interface */
+ (*task)->Release (task);
+
+ /* Release exclusive access */
+ (*interface)->ReleaseExclusiveAccess (interface);
+
+ return returnValue;
+ }
+
+ static SANE_Status
+ sanei_scsi_cmd2_stuc_api (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+ CFDataRef guid;
+ mach_port_t masterPort;
+ int i;
+ io_object_t scsiDevice;
+ SInt32 peripheralDeviceType;
+ CFMutableDictionaryRef matchingDict;
+ io_iterator_t iokIterator;
+ IOReturn ioReturnValue;
+ IOCFPlugInInterface **plugInInterface = NULL;
+ SCSITaskDeviceInterface **interface = NULL;
+ io_object_t nextDevice;
+ SANE_Status returnValue;
+
+ guid = fd_info[fd].pdata;
+ if (!guid)
+ {
+ DBG (5, "No GUID\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ DBG (2, "cmd2: cmd_size:%ld src_size:%ld dst_size:%ld isWrite:%d\n",
+ cmd_size, src_size, (!dst_size) ? 0 : *dst_size, (!dst_size) ? 1 : 0);
+
+ /* Use default master port */
+ masterPort = 0;
+ ioReturnValue = IOMasterPort (MACH_PORT_NULL, &masterPort);
+ if (ioReturnValue != kIOReturnSuccess || masterPort == 0)
+ {
+ DBG (5, "Could not get I/O master port (0x%08x)\n", ioReturnValue);
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ /* Search for both Scanner type and Processor type devices */
+ /* GB TDB This should only be needed for find */
+ scsiDevice = 0;
+ for (i = 0; !scsiDevice && i < 2; i++)
+ {
+ peripheralDeviceType =
+ (i == 0 ? kINQUIRY_PERIPHERAL_TYPE_ScannerSCSI2Device :
+ kINQUIRY_PERIPHERAL_TYPE_ProcessorSPCDevice);
+
+ /* Set up a matching dictionary to search the I/O Registry for
+ the SCSI device */
+ /* we are interested in, specifying the SCSITaskUserClient GUID. */
+ matchingDict = NULL;
+ CreateMatchingDictionaryForSTUC (peripheralDeviceType, NULL, NULL,
+ guid, &matchingDict);
+ if (matchingDict == NULL)
+ {
+ DBG (5, "CreateMatchingDictionaryForSTUC Failed\n");
+ return SANE_STATUS_NO_MEM;
+ }
+
+ /* Now search I/O Registry for the matching device */
+ iokIterator = 0;
+ ioReturnValue =
+ IOServiceGetMatchingServices (masterPort, matchingDict,
+ &iokIterator);
+ if (ioReturnValue != kIOReturnSuccess)
+ {
+ DBG (5, "IOServiceGetMatchingServices Failed\n");
+ return SANE_STATUS_NO_MEM;
+ }
+
+ scsiDevice = IOIteratorNext (iokIterator);
+
+ while ((nextDevice = IOIteratorNext (iokIterator)))
+ {
+ IOObjectRelease (nextDevice);
+ }
+
+ IOObjectRelease (iokIterator);
+ }
+
+ if (!scsiDevice)
+ {
+ DBG (5, "Device not found\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ /* Found Device */
+ /* Create interface */
+
+ CreateDeviceInterfaceUsingSTUC (scsiDevice, &plugInInterface, &interface);
+
+ /* Done with SCSI object from I/O Registry. */
+ ioReturnValue = IOObjectRelease (scsiDevice);
+
+ returnValue = SANE_STATUS_IO_ERROR;
+
+ if (ioReturnValue != kIOReturnSuccess)
+ {
+ DBG (5, "Error releasing SCSI device. (0x%08x)\n", ioReturnValue);
+ }
+ else if (interface != NULL)
+ {
+ /* Execute the command */
+ returnValue =
+ ExecuteCommandUsingSTUC (interface, cmd, cmd_size, src, src_size,
+ dst, dst_size);
+ }
+
+ if (interface != NULL)
+ {
+ (*interface)->Release (interface);
+ }
+
+ if (plugInInterface != NULL)
+ {
+ IODestroyPlugInInterface (plugInInterface);
+ }
+
+ return returnValue;
+ }
+
+ static void
+ sanei_scsi_find_devices_stuc_api (const char *findvendor,
+ const char *findmodel,
+ const char *findtype, int findbus,
+ int findchannel, int findid, int findlun,
+ SANE_Status (*attach) (const char *dev))
+ {
+ mach_port_t masterPort;
+ IOReturn ioReturnValue;
+ int i;
+ SInt32 peripheralDeviceType;
+ CFMutableDictionaryRef matchingDict;
+ io_iterator_t iokIterator;
+ io_object_t scsiDevice;
+ CFDataRef GUIDRef;
+ char *devname;
+ int len;
+ const unsigned char *p;
+ CFDictionaryRef protocolCharacteristics;
+ CFNumberRef scsiLunRef;
+ int scsilun;
+
+ masterPort = 0;
+ ioReturnValue = IOMasterPort (MACH_PORT_NULL, &masterPort);
+ if (ioReturnValue != kIOReturnSuccess || masterPort == 0)
+ return;
+
+ DBG (5, "Search for Vendor: %s Model: %s\n",
+ (findvendor) ? findvendor : "(none)",
+ (findmodel) ? findmodel : "(none)");
+
+ /* Search for both Scanner type and Processor type devices */
+
+ for (i = 0; i < 2; i++)
+ {
+ peripheralDeviceType =
+ (i == 0 ? kINQUIRY_PERIPHERAL_TYPE_ScannerSCSI2Device :
+ kINQUIRY_PERIPHERAL_TYPE_ProcessorSPCDevice);
+
+ /* Set up a matching dictionary to search the I/O Registry for SCSI
+ devices we are interested in. */
+
+ matchingDict = NULL;
+ CreateMatchingDictionaryForSTUC (peripheralDeviceType, findvendor,
+ findmodel, NULL, &matchingDict);
+ if (matchingDict == NULL)
+ {
+ DBG (5, "CreateMatchingDictionaryForSTUC Failed\n");
+ return;
+ }
+
+ /* Now search I/O Registry for matching devices. */
+
+ iokIterator = 0;
+ ioReturnValue =
+ IOServiceGetMatchingServices (masterPort, matchingDict,
+ &iokIterator);
+ if (ioReturnValue != kIOReturnSuccess)
+ {
+ DBG (5, "IOServiceGetMatchingServices Failed\n");
+ return;
+ }
+
+ /* Check devices */
+
+ while ((scsiDevice = IOIteratorNext (iokIterator)))
+ {
+ scsilun = 0;
+ protocolCharacteristics = IORegistryEntryCreateCFProperty
+ (scsiDevice, CFSTR ("Protocol Characteristics"), NULL, 0);
+ if (protocolCharacteristics)
+ {
+ scsiLunRef = CFDictionaryGetValue
+ (protocolCharacteristics,
+ CFSTR ("SCSI Logical Unit Number"));
+ if (scsiLunRef)
+ CFNumberGetValue (scsiLunRef, kCFNumberIntType, &scsilun);
+ CFRelease (protocolCharacteristics);
+ }
+
+ if (findlun < 0 || findlun == scsilun)
+ {
+ /* Create device name from the SCSITaskUserClient GUID */
+
+ GUIDRef = IORegistryEntryCreateCFProperty
+ (scsiDevice,
+ CFSTR (kIOPropertySCSITaskUserClientInstanceGUID),
+ NULL, 0);
+
+ if (GUIDRef)
+ {
+ len = CFDataGetLength (GUIDRef);
+ p = CFDataGetBytePtr (GUIDRef);
+
+ devname = (char *) malloc (2 * len + 3);
+ devname [0] = '<';
+ for (i = 0; i < len; i++)
+ sprintf (&devname [2 * i + 1], "%02x", p [i]);
+ devname [2 * len + 1] = '>';
+ devname [2 * len + 2] = '\0';
+
+ CFRelease (GUIDRef);
+
+ DBG (1, "Found: %s\n", devname);
+
+ /* Attach to the device */
+ (*attach) (devname);
+ free (devname);
+ }
+ else
+ DBG (1, "Can't find SCSITaskUserClient GUID\n");
+ }
+ }
+ IOObjectRelease (iokIterator);
+ }
+ }
+
+# endif /* HAVE_IOKIT_SCSI_COMMANDS_SCSICOMMANDOPERATIONCODES_H */
+
+ SANE_Status
+ sanei_scsi_cmd2 (int fd,
+ const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+ if (fd_info[fd].pdata)
+# if defined (HAVE_IOKIT_SCSI_SCSICOMMANDOPERATIONCODES_H) || \
+ defined (HAVE_IOKIT_SCSI_COMMANDS_SCSICOMMANDOPERATIONCODES_H)
+ return sanei_scsi_cmd2_stuc_api (fd, cmd, cmd_size, src, src_size,
+ dst, dst_size);
+# else
+ return SANE_STATUS_INVAL;
+# endif
+ else
+# ifdef HAVE_IOKIT_CDB_IOSCSILIB_H
+ return sanei_scsi_cmd2_old_api (fd, cmd, cmd_size, src, src_size,
+ dst, dst_size);
+# else
+ return SANE_STATUS_INVAL;
+# endif
+ }
+
+ void
+ sanei_scsi_find_devices (const char *findvendor, const char *findmodel,
+ const char *findtype,
+ int findbus, int findchannel, int findid,
+ int findlun,
+ SANE_Status (*attach) (const char *dev))
+ {
+# if defined (HAVE_IOKIT_SCSI_SCSICOMMANDOPERATIONCODES_H) || \
+ defined (HAVE_IOKIT_SCSI_COMMANDS_SCSICOMMANDOPERATIONCODES_H)
+ sanei_scsi_find_devices_stuc_api (findvendor, findmodel, findtype,
+ findbus, findchannel, findid,
+ findlun, attach);
+# endif
+# ifdef HAVE_IOKIT_CDB_IOSCSILIB_H
+ sanei_scsi_find_devices_old_api (findvendor, findmodel, findtype,
+ findbus, findchannel, findid,
+ findlun, attach);
+# endif
+ }
+
+#define WE_HAVE_FIND_DEVICES
+
+#endif /* USE == MACOSX_INTERFACE */
+
+
+#ifndef WE_HAVE_ASYNC_SCSI
+
+ SANE_Status
+ sanei_scsi_req_enter2 (int fd, const void *cmd, size_t cmd_size,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size, void **idp)
+ {
+ return sanei_scsi_cmd2 (fd, cmd, cmd_size, src, src_size, dst, dst_size);
+ }
+
+ SANE_Status sanei_scsi_req_wait (void *id)
+ {
+ return SANE_STATUS_GOOD;
+ }
+
+ void sanei_scsi_req_flush_all (void)
+ {
+ }
+
+ void sanei_scsi_req_flush_all_extended (int fd)
+ {
+ }
+
+#endif /* WE_HAVE_ASYNC_SCSI */
+
+ SANE_Status sanei_scsi_req_enter (int fd,
+ const void *src, size_t src_size,
+ void *dst, size_t * dst_size, void **idp)
+ {
+ size_t cmd_size = CDB_SIZE (*(const char *) src);
+
+ if (dst_size && *dst_size)
+ assert (src_size == cmd_size);
+ else
+ assert (src_size >= cmd_size);
+
+ return sanei_scsi_req_enter2 (fd, src, cmd_size,
+ (const char *) src + cmd_size,
+ src_size - cmd_size, dst, dst_size, idp);
+ }
+
+ SANE_Status
+ sanei_scsi_cmd (int fd, const void *src, size_t src_size,
+ void *dst, size_t * dst_size)
+ {
+ size_t cmd_size = CDB_SIZE (*(const char *) src);
+
+ if (dst_size && *dst_size)
+ assert (src_size == cmd_size);
+ else
+ assert (src_size >= cmd_size);
+
+ return sanei_scsi_cmd2 (fd, src, cmd_size,
+ (const char *) src + cmd_size,
+ src_size - cmd_size, dst, dst_size);
+ }
+
+
+
+#ifndef WE_HAVE_FIND_DEVICES
+
+ void
+ sanei_scsi_find_devices (const char *findvendor, const char *findmodel,
+ const char *findtype,
+ int findbus, int findchannel, int findid,
+ int findlun,
+ SANE_Status (*attach) (const char *dev))
+ {
+ DBG_INIT ();
+ DBG (1, "sanei_scsi_find_devices: not implemented for this platform\n");
+ }
+
+#endif /* WE_HAVE_FIND_DEVICES */
diff --git a/sanei/sanei_usb.c b/sanei/sanei_usb.c
new file mode 100644
index 0000000..f50a56c
--- /dev/null
+++ b/sanei/sanei_usb.c
@@ -0,0 +1,3381 @@
+/* sane - Scanner Access Now Easy.
+ Copyright (C) 2001 - 2005 Henning Meier-Geinitz
+ Copyright (C) 2001 Frank Zago (sanei_usb_control_msg)
+ Copyright (C) 2003 Rene Rebe (sanei_read_int,sanei_set_timeout)
+ Copyright (C) 2005 Paul Smedley <paul@smedley.info> (OS/2 usbcalls)
+ Copyright (C) 2007 EPSON AVASYS CORPORATION (usblp detach/attach)
+ Copyright (C) 2008 m. allan noah (bus rescan support, sanei_usb_clear_halt)
+ Copyright (C) 2009 Julien BLACHE <jb@jblache.org> (libusb-1.0)
+ Copyright (C) 2011 Reinhold Kainhofer <reinhold@kainhofer.com> (sanei_usb_set_endpoint)
+ This file is part of the SANE package.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+
+ As a special exception, the authors of SANE give permission for
+ additional uses of the libraries contained in this release of SANE.
+
+ The exception is that, if you link a SANE library with other files
+ to produce an executable, this does not by itself cause the
+ resulting executable to be covered by the GNU General Public
+ License. Your use of that executable is in no way restricted on
+ account of linking the SANE library code into it.
+
+ This exception does not, however, invalidate any other reasons why
+ the executable file might be covered by the GNU General Public
+ License.
+
+ If you submit changes to SANE to the maintainers to be included in
+ a subsequent release, you agree by submitting the changes that
+ those changes may be distributed with this exception intact.
+
+ If you write modifications of your own for SANE, it is your choice
+ whether to permit this exception to apply to your modifications.
+ If you do not wish that, delete this exception notice.
+
+ This file provides a generic USB interface. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <time.h>
+
+#ifdef HAVE_RESMGR
+#include <resmgr.h>
+#endif
+
+#ifdef HAVE_LIBUSB
+#include <usb.h>
+#endif /* HAVE_LIBUSB */
+
+#ifdef HAVE_LIBUSB_1_0
+#include <libusb.h>
+#endif /* HAVE_LIBUSB_1_0 */
+
+#ifdef HAVE_USBCALLS
+#include <usb.h>
+#include <os2.h>
+#include <usbcalls.h>
+#define MAX_RW 64000
+static int usbcalls_timeout = 30 * 1000; /* 30 seconds */
+USBHANDLE dh;
+PHEV pUsbIrqStartHev=NULL;
+
+static
+struct usb_descriptor_header *
+GetNextDescriptor( struct usb_descriptor_header *currHead, UCHAR *lastBytePtr)
+{
+ UCHAR *currBytePtr, *nextBytePtr;
+
+ if (!currHead->bLength)
+ return (NULL);
+ currBytePtr=(UCHAR *)currHead;
+ nextBytePtr=currBytePtr+currHead->bLength;
+ if (nextBytePtr>=lastBytePtr)
+ return (NULL);
+ return ((struct usb_descriptor_header*)nextBytePtr);
+}
+#endif /* HAVE_USBCALLS */
+
+#if defined (__FreeBSD__)
+#include <dev/usb/usb.h>
+#endif /* __FreeBSD__ */
+#if defined (__DragonFly__)
+#include <bus/usb/usb.h>
+#endif
+
+#ifdef BACKEND_NAME
+#undef BACKEND_NAME
+#endif
+
+#define BACKEND_NAME sanei_usb
+#include <sane/sane.h>
+#include "sane/sanei_debug.h"
+#include "sane/sanei_usb.h"
+#include "sane/sanei_config.h"
+
+typedef enum
+{
+ sanei_usb_method_scanner_driver = 0, /* kernel scanner driver
+ (Linux, BSD) */
+ sanei_usb_method_libusb,
+
+ sanei_usb_method_usbcalls
+}
+sanei_usb_access_method_type;
+
+typedef struct
+{
+ SANE_Bool open;
+ sanei_usb_access_method_type method;
+ int fd;
+ SANE_String devname;
+ SANE_Int vendor;
+ SANE_Int product;
+ SANE_Int bulk_in_ep;
+ SANE_Int bulk_out_ep;
+ SANE_Int iso_in_ep;
+ SANE_Int iso_out_ep;
+ SANE_Int int_in_ep;
+ SANE_Int int_out_ep;
+ SANE_Int control_in_ep;
+ SANE_Int control_out_ep;
+ SANE_Int interface_nr;
+ SANE_Int missing;
+#ifdef HAVE_LIBUSB
+ usb_dev_handle *libusb_handle;
+ struct usb_device *libusb_device;
+#endif /* HAVE_LIBUSB */
+#ifdef HAVE_LIBUSB_1_0
+ libusb_device *lu_device;
+ libusb_device_handle *lu_handle;
+#endif /* HAVE_LIBUSB_1_0 */
+#if defined (__linux__) && (defined(HAVE_LIBUSB) || defined(HAVE_LIBUSB_1_0))
+#if defined (USB_MAXINTERFACES)
+#define MAX_INTERFACE_FLAGS ((USB_MAXINTERFACES + 7) / 8)
+#else
+#define MAX_INTERFACE_FLAGS (256 / 8)
+#endif
+ char driver_detached[MAX_INTERFACE_FLAGS];
+#endif /* __linux__ && (HAVE_LIBUSB || HAVE_LIBUSB_1_0) */
+}
+device_list_type;
+
+/**
+ * total number of devices that can be found at the same time */
+#define MAX_DEVICES 100
+
+/**
+ * per-device information, using the functions' parameters dn as index */
+static device_list_type devices[MAX_DEVICES];
+
+/**
+ * total number of detected devices in devices array */
+static int device_number=0;
+
+#if defined(HAVE_LIBUSB) || defined(HAVE_LIBUSB_1_0)
+static int libusb_timeout = 30 * 1000; /* 30 seconds */
+#endif /* HAVE_LIBUSB || HAVE_LIBUSB_1_0 */
+
+#ifdef HAVE_LIBUSB
+#if defined (__linux__)
+#ifndef USB_ERROR_STR
+#define USB_ERROR_STR(x, format, args...) \
+ do {\
+ DBG (2, format, ## args);\
+ return x;\
+ } while (0)
+#endif
+
+/* From libusb-0.1.12/linux.[ch].
+ Used to detach and (re)attach kernel drivers. */
+struct usb_ioctl {
+ int ifno; /* interface 0..N ; negative numbers reserved */
+ int ioctl_code; /* MUST encode size + direction of data so the
+ * macros in <asm/ioctl.h> give correct values */
+ void *data; /* param buffer (in, or out) */
+};
+
+#define USB_MAXDRIVERNAME 255
+
+struct usb_getdriver {
+ unsigned int interface;
+ char driver[USB_MAXDRIVERNAME + 1];
+};
+
+#define IOCTL_USB_IOCTL _IOWR('U', 18, struct usb_ioctl)
+#define IOCTL_USB_GETDRIVER _IOW('U', 8, struct usb_getdriver)
+#define IOCTL_USB_DISCONNECT _IO('U', 22)
+#define IOCTL_USB_CONNECT _IO('U', 23)
+
+struct usb_dev_handle { int fd; };
+
+#ifndef LIBUSB_HAS_USB_GET_DRIVER_NP
+static int
+_usb_get_driver_np (usb_dev_handle *dev, int interface, char *name,
+ unsigned int namelen)
+{
+ struct usb_getdriver getdrv;
+ int ret;
+
+ getdrv.interface = interface;
+ ret = ioctl (dev->fd, IOCTL_USB_GETDRIVER, &getdrv);
+ if (ret)
+ USB_ERROR_STR (-errno, "could not get bound driver: %s", strerror(errno));
+
+ strncpy (name, getdrv.driver, namelen - 1);
+ name[namelen - 1] = 0;
+
+ return 0;
+}
+#define sanei_usb_get_driver_np _usb_get_driver_np
+#else
+#define sanei_usb_get_driver_np usb_get_driver_np
+#endif /* LIBUSB_HAS_USB_GET_DRIVER_NP */
+
+#ifndef LIBUSB_HAS_USB_DETACH_KERNEL_DRIVER_NP
+static int
+_usb_detach_kernel_driver_np (usb_dev_handle *dev, int interface)
+{
+ struct usb_ioctl command;
+ int ret;
+
+ command.ifno = interface;
+ command.ioctl_code = IOCTL_USB_DISCONNECT;
+ command.data = NULL;
+
+ ret = ioctl (dev->fd, IOCTL_USB_IOCTL, &command);
+ if (ret)
+ USB_ERROR_STR (-errno,
+ "could not detach kernel driver from interface %d: %s",
+ interface, strerror(errno));
+
+ return 0;
+}
+#define sanei_usb_detach_kernel_driver_np _usb_detach_kernel_driver_np
+#else
+#define sanei_usb_detach_kernel_driver_np usb_detach_kernel_driver_np
+#endif /* LIBUSB_HAS_USB_DETACH_KERNEL_DRIVER_NP */
+
+#ifndef LIBUSB_HAS_USB_ATTACH_KERNEL_DRIVER_NP
+static int
+_usb_attach_kernel_driver_np (struct usb_dev_handle *dev, int interface)
+{
+ struct usb_ioctl command;
+ int ret;
+
+ command.ifno = interface;
+ command.ioctl_code = IOCTL_USB_CONNECT;
+ command.data = NULL;
+
+ ret = ioctl(dev->fd, IOCTL_USB_IOCTL, &command);
+ if (ret)
+ USB_ERROR_STR(-errno,
+ "could not attach kernel driver to interface %d: %s",
+ interface, strerror(errno));
+ return 0;
+}
+#define sanei_usb_attach_kernel_driver_np _usb_attach_kernel_driver_np
+#else
+#define sanei_usb_attach_kernel_driver_np usb_attach_kernel_driver_np
+#endif /* LIBUSB_HAS_USB_ATTACH_KERNEL_DRIVER_NP */
+
+static void
+detach_usblp_driver (usb_dev_handle *h, char *detached)
+{
+ const int c = 0; /* only config ever tried */
+
+ int i;
+ struct usb_device *d = usb_device (h);
+
+ for (i = 0; i < d->config[c].bNumInterfaces; ++i)
+ {
+ char driver[32];
+
+ driver[0] = 0;
+ sanei_usb_get_driver_np (h, i, driver, sizeof (driver));
+
+ if ((0 != driver[0]) && (0 == strcasecmp ("usblp", driver)))
+ {
+ detached[i/8] |= (1 << (i%8));
+ DBG (4, "Detaching '%s' from config/interface %03i/%03i\n",
+ driver, c, i);
+ if (0 > sanei_usb_detach_kernel_driver_np (h, i))
+ {
+ DBG (4, "Failed to detach '%s'\n", driver);
+ }
+ }
+ }
+}
+
+static void
+attach_usblp_driver (usb_dev_handle *h, char *detached)
+{
+ const int c = 0; /* only config ever tried */
+
+ int i;
+ struct usb_device *d = usb_device (h);
+
+ for (i = 0; i < d->config[c].bNumInterfaces; ++i)
+ {
+ if ((1 << (i%8)) & detached[i/8])
+ {
+ DBG (4, "Attaching drivers on config/interface %03i/%03i\n",
+ c, i);
+ if (0 > sanei_usb_attach_kernel_driver_np (h, i))
+ {
+ DBG (4, "Failed to attach drivers\n");
+ }
+ break;
+ }
+ }
+}
+#endif /* __linux__ */
+#endif /* HAVE_LIBUSB */
+
+#ifdef HAVE_LIBUSB_1_0
+static char *sanei_libusb_strerror (int errcode);
+#endif /* HAVE_LIBUSB_1_0 */
+
+#if defined (HAVE_LIBUSB_1_0) && defined (__linux__)
+static void
+detach_usblp_driver (libusb_device_handle *h, char *detached)
+{
+ const int c = 0; /* only config ever tried */
+
+ int i;
+ libusb_device *d = libusb_get_device (h);
+ struct libusb_config_descriptor *cfg = NULL;
+ int ret = libusb_get_config_descriptor (d, c, &cfg);
+ if (LIBUSB_SUCCESS != ret)
+ {
+ DBG (4, "Failed to get config descriptor; '%s'\n", sanei_libusb_strerror (ret));
+ return;
+ }
+
+ for (i = 0; i < cfg->bNumInterfaces; ++i)
+ {
+ const int is_active = 1;
+ ret = libusb_kernel_driver_active (h, i);
+ if (is_active == ret)
+ {
+ detached[i/8] |= (1 << (i%8));
+ DBG (4, "Detaching kernel driver from config/interface %03i/%03i\n",
+ c, i);
+ ret = libusb_detach_kernel_driver (h, i);
+ if (LIBUSB_SUCCESS != ret)
+ {
+ DBG (4, "Failed to detach kernel driver; '%s'\n", sanei_libusb_strerror (ret));
+ }
+ }
+ else if (0 != ret)
+ {
+ DBG (4, "Failed to find kernel driver; '%s'\n", sanei_libusb_strerror (ret));
+ }
+ }
+}
+
+static void
+attach_usblp_driver (libusb_device_handle *h, char *detached)
+{
+ const int c = 0; /* only config ever tried */
+
+ int i;
+ libusb_device *d = libusb_get_device (h);
+ struct libusb_config_descriptor *cfg = NULL;
+ int ret = libusb_get_config_descriptor (d, c, &cfg);
+ if (LIBUSB_SUCCESS != ret)
+ {
+ DBG (4, "Failed to get config descriptor; '%s'\n", sanei_libusb_strerror (ret));
+ return;
+ }
+
+ for (i = 0; i < cfg->bNumInterfaces; ++i)
+ {
+ if ((1 << (i%8)) & detached[i/8])
+ {
+ DBG (4, "Attaching drivers on config/interface %03i/%03i\n",
+ c, i);
+ ret = libusb_attach_kernel_driver (h, i);
+ if (LIBUSB_SUCCESS != ret)
+ {
+ DBG (4, "Failed to attach drivers; '%s'\n", sanei_libusb_strerror (ret));
+ }
+ break;
+ }
+ }
+}
+#endif /* HAVE_LIBUSB_1_0 && __linux__ */
+
+#ifdef HAVE_LIBUSB_1_0
+static libusb_context *sanei_usb_ctx;
+#endif /* HAVE_LIBUSB_1_0 */
+
+#if defined (__linux__)
+/* From /usr/src/linux/driver/usb/scanner.h */
+#define SCANNER_IOCTL_VENDOR _IOR('U', 0x20, int)
+#define SCANNER_IOCTL_PRODUCT _IOR('U', 0x21, int)
+#define SCANNER_IOCTL_CTRLMSG _IOWR('U', 0x22, devrequest)
+/* Older (unofficial) IOCTL numbers for Linux < v2.4.13 */
+#define SCANNER_IOCTL_VENDOR_OLD _IOR('u', 0xa0, int)
+#define SCANNER_IOCTL_PRODUCT_OLD _IOR('u', 0xa1, int)
+
+/* From /usr/src/linux/include/linux/usb.h */
+typedef struct
+{
+ unsigned char requesttype;
+ unsigned char request;
+ unsigned short value;
+ unsigned short index;
+ unsigned short length;
+}
+devrequest;
+
+/* From /usr/src/linux/driver/usb/scanner.h */
+struct ctrlmsg_ioctl
+{
+ devrequest req;
+ void *data;
+}
+cmsg;
+#elif defined(__BEOS__)
+#include <drivers/USB_scanner.h>
+#include <kernel/OS.h>
+#endif /* __linux__ */
+
+/* Debug level from sanei_init_debug */
+static SANE_Int debug_level;
+
+static void
+print_buffer (const SANE_Byte * buffer, SANE_Int size)
+{
+#define NUM_COLUMNS 16
+#define PRINT_BUFFER_SIZE (4 + NUM_COLUMNS * (3 + 1) + 1 + 1)
+ char line_str[PRINT_BUFFER_SIZE];
+ char *pp;
+ int column;
+ int line;
+
+ memset (line_str, 0, PRINT_BUFFER_SIZE);
+
+ for (line = 0; line < ((size + NUM_COLUMNS - 1) / NUM_COLUMNS); line++)
+ {
+ pp = line_str;
+ sprintf (pp, "%03X ", line * NUM_COLUMNS);
+ pp += 4;
+ for (column = 0; column < NUM_COLUMNS; column++)
+ {
+ if ((line * NUM_COLUMNS + column) < size)
+ sprintf (pp, "%02X ", buffer[line * NUM_COLUMNS + column]);
+ else
+ sprintf (pp, " ");
+ pp += 3;
+ }
+ for (column = 0; column < NUM_COLUMNS; column++)
+ {
+ if ((line * NUM_COLUMNS + column) < size)
+ sprintf (pp, "%c",
+ (buffer[line * NUM_COLUMNS + column] < 127) &&
+ (buffer[line * NUM_COLUMNS + column] > 31) ?
+ buffer[line * NUM_COLUMNS + column] : '.');
+ else
+ sprintf (pp, " ");
+ pp += 1;
+ }
+ DBG (11, "%s\n", line_str);
+ }
+}
+
+static void
+kernel_get_vendor_product (int fd, const char *name, int *vendorID, int *productID)
+{
+#if defined (__linux__)
+ /* read the vendor and product IDs via the IOCTLs */
+ if (ioctl (fd, SCANNER_IOCTL_VENDOR, vendorID) == -1)
+ {
+ if (ioctl (fd, SCANNER_IOCTL_VENDOR_OLD, vendorID) == -1)
+ DBG (3, "kernel_get_vendor_product: ioctl (vendor) "
+ "of device %s failed: %s\n", name, strerror (errno));
+ }
+ if (ioctl (fd, SCANNER_IOCTL_PRODUCT, productID) == -1)
+ {
+ if (ioctl (fd, SCANNER_IOCTL_PRODUCT_OLD, productID) == -1)
+ DBG (3, "sanei_usb_get_vendor_product: ioctl (product) "
+ "of device %s failed: %s\n", name, strerror (errno));
+ }
+#elif defined(__BEOS__)
+ {
+ uint16 vendor, product;
+ if (ioctl (fd, B_SCANNER_IOCTL_VENDOR, &vendor) != B_OK)
+ DBG (3, "kernel_get_vendor_product: ioctl (vendor) "
+ "of device %d failed: %s\n", fd, strerror (errno));
+ if (ioctl (fd, B_SCANNER_IOCTL_PRODUCT, &product) != B_OK)
+ DBG (3, "sanei_usb_get_vendor_product: ioctl (product) "
+ "of device %d failed: %s\n", fd, strerror (errno));
+ /* copy from 16 to 32 bit value */
+ *vendorID = vendor;
+ *productID = product;
+ }
+#elif defined (__FreeBSD__) || defined (__DragonFly__)
+ {
+ int controller;
+ int ctrl_fd;
+ char buf[40];
+ int dev;
+
+ for (controller = 0; ; controller++ )
+ {
+ snprintf (buf, sizeof (buf) - 1, "/dev/usb%d", controller);
+ ctrl_fd = open (buf, O_RDWR);
+
+ /* If we can not open the usb controller device, treat it
+ as the end of controller devices */
+ if (ctrl_fd < 0)
+ break;
+
+ /* Search for the scanner device on this bus */
+ for (dev = 1; dev < USB_MAX_DEVICES; dev++)
+ {
+ struct usb_device_info devInfo;
+ devInfo.udi_addr = dev;
+
+ if (ioctl (ctrl_fd, USB_DEVICEINFO, &devInfo) == -1)
+ break; /* Treat this as the end of devices for this controller */
+
+ snprintf (buf, sizeof (buf), "/dev/%s", devInfo.udi_devnames[0]);
+ if (strncmp (buf, name, sizeof (buf)) == 0)
+ {
+ *vendorID = (int) devInfo.udi_vendorNo;
+ *productID = (int) devInfo.udi_productNo;
+ close (ctrl_fd);
+ return;
+ }
+ }
+ close (ctrl_fd);
+ }
+ DBG (3, "kernel_get_vendor_product: Could not retrieve "
+ "vendor/product ID from device %s\n", name);
+ }
+#endif /* defined (__linux__), defined(__BEOS__), ... */
+ /* put more os-dependant stuff ... */
+}
+
+/**
+ * store the given device in device list if it isn't already
+ * in it
+ * @param device device to store if new
+ */
+static void
+store_device (device_list_type device)
+{
+ int i = 0;
+ int pos = -1;
+
+ /* if there are already some devices present, check against
+ * them and leave if an equal one is found */
+ for (i = 0; i < device_number; i++)
+ {
+ if (devices[i].method == device.method
+ && !strcmp (devices[i].devname, device.devname)
+ && devices[i].vendor == device.vendor
+ && devices[i].product == device.product)
+ {
+ /*
+ * Need to update the LibUSB device pointer, since it might
+ * have changed after the latest USB scan.
+ */
+#ifdef HAVE_LIBUSB
+ devices[i].libusb_device = device.libusb_device;
+#endif
+#ifdef HAVE_LIBUSB_1_0
+ devices[i].lu_device = device.lu_device;
+#endif
+
+ devices[i].missing=0;
+ DBG (3, "store_device: not storing device %s\n", device.devname);
+ return;
+ }
+ if (devices[i].missing >= 2)
+ pos = i;
+ }
+
+ if(pos > -1){
+ DBG (3, "store_device: overwrite dn %d with %s\n", pos, device.devname);
+ }
+ else{
+ if(device_number >= MAX_DEVICES){
+ DBG (3, "store_device: no room for %s\n", device.devname);
+ return;
+ }
+ pos = device_number;
+ device_number++;
+ DBG (3, "store_device: add dn %d with %s\n", pos, device.devname);
+ }
+ memcpy (&(devices[pos]), &device, sizeof (device));
+ devices[pos].open = SANE_FALSE;
+}
+
+#ifdef HAVE_LIBUSB_1_0
+static char *
+sanei_libusb_strerror (int errcode)
+{
+ /* Error codes & descriptions from the libusb-1.0 documentation */
+
+ switch (errcode)
+ {
+ case LIBUSB_SUCCESS:
+ return "Success (no error)";
+
+ case LIBUSB_ERROR_IO:
+ return "Input/output error";
+
+ case LIBUSB_ERROR_INVALID_PARAM:
+ return "Invalid parameter";
+
+ case LIBUSB_ERROR_ACCESS:
+ return "Access denied (insufficient permissions)";
+
+ case LIBUSB_ERROR_NO_DEVICE:
+ return "No such device (it may have been disconnected)";
+
+ case LIBUSB_ERROR_NOT_FOUND:
+ return "Entity not found";
+
+ case LIBUSB_ERROR_BUSY:
+ return "Resource busy";
+
+ case LIBUSB_ERROR_TIMEOUT:
+ return "Operation timed out";
+
+ case LIBUSB_ERROR_OVERFLOW:
+ return "Overflow";
+
+ case LIBUSB_ERROR_PIPE:
+ return "Pipe error";
+
+ case LIBUSB_ERROR_INTERRUPTED:
+ return "System call interrupted (perhaps due to signal)";
+
+ case LIBUSB_ERROR_NO_MEM:
+ return "Insufficient memory";
+
+ case LIBUSB_ERROR_NOT_SUPPORTED:
+ return "Operation not supported or unimplemented on this platform";
+
+ case LIBUSB_ERROR_OTHER:
+ return "Other error";
+
+ default:
+ return "Unknown libusb-1.0 error code";
+ }
+
+ return "Unknown libusb-1.0 error code";
+}
+#endif /* HAVE_LIBUSB_1_0 */
+
+void
+sanei_usb_init (void)
+{
+ SANE_String *prefix;
+ SANE_String prefixlist[] = {
+#if defined(__linux__)
+ "/dev/", "usbscanner",
+ "/dev/usb/", "scanner",
+#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined (__OpenBSD__) || defined (__DragonFly__)
+ "/dev/", "uscanner",
+#elif defined(__BEOS__)
+ "/dev/scanner/usb/", "",
+#endif
+ 0, 0
+ };
+ SANE_Int vendor, product;
+ SANE_Char devname[1024];
+ int fd;
+ int i;
+ device_list_type device;
+#ifdef HAVE_LIBUSB
+ struct usb_bus *bus;
+ struct usb_device *dev;
+#endif /* HAVE_LIBUSB */
+#ifdef HAVE_LIBUSB_1_0
+ libusb_device **devlist;
+ ssize_t ndev;
+
+ libusb_device *dev;
+ libusb_device_handle *hdl;
+ struct libusb_device_descriptor desc;
+ struct libusb_config_descriptor *config0;
+ unsigned short vid, pid;
+ unsigned char busno, address;
+ int config;
+ int interface;
+ int ret;
+#endif /* HAVE_LIBUSB_1_0 */
+
+ DBG_INIT ();
+#ifdef DBG_LEVEL
+ debug_level = DBG_LEVEL;
+#else
+ debug_level = 0;
+#endif
+
+ /* if no device yet, clean up memory */
+ if(device_number==0)
+ memset (devices, 0, sizeof (devices));
+
+ DBG (4, "sanei_usb_init: marking existing devices\n");
+ for (i = 0; i < device_number; i++) {
+ devices[i].missing++;
+ }
+
+ DBG (4, "sanei_usb_init: Looking for kernel scanner devices\n");
+ /* Check for devices using the kernel scanner driver */
+
+ for (prefix = prefixlist; *prefix; prefix += 2)
+ {
+ SANE_String dir_name = *prefix;
+ SANE_String base_name = *(prefix + 1);
+ struct stat stat_buf;
+ DIR *dir;
+ struct dirent *dir_entry;
+
+ if (stat (dir_name, &stat_buf) < 0)
+ {
+ DBG (5, "sanei_usb_init: can't stat %s: %s\n", dir_name,
+ strerror (errno));
+ continue;
+ }
+ if (!S_ISDIR (stat_buf.st_mode))
+ {
+ DBG (5, "sanei_usb_init: %s is not a directory\n", dir_name);
+ continue;
+ }
+ if ((dir = opendir (dir_name)) == 0)
+ {
+ DBG (5, "sanei_usb_init: cannot read directory %s: %s\n", dir_name,
+ strerror (errno));
+ continue;
+ }
+
+ while ((dir_entry = readdir (dir)) != 0)
+ {
+ /* skip standard dir entries */
+ if (strcmp (dir_entry->d_name, ".") == 0 || strcmp (dir_entry->d_name, "..") == 0)
+ continue;
+
+ if (strncmp (base_name, dir_entry->d_name, strlen (base_name)) == 0)
+ {
+ if (strlen (dir_name) + strlen (dir_entry->d_name) + 1 >
+ sizeof (devname))
+ continue;
+ sprintf (devname, "%s%s", dir_name, dir_entry->d_name);
+ fd = -1;
+#ifdef HAVE_RESMGR
+ fd = rsm_open_device (devname, O_RDWR);
+#endif
+ if (fd == -1)
+ fd = open (devname, O_RDWR);
+ if (fd < 0)
+ {
+ DBG (5, "sanei_usb_init: couldn't open %s: %s\n", devname,
+ strerror (errno));
+ continue;
+ }
+ vendor = -1;
+ product = -1;
+ kernel_get_vendor_product (fd, devname, &vendor, &product);
+ close (fd);
+ memset (&device, 0, sizeof (device));
+ device.devname = strdup (devname);
+ if (!device.devname)
+ {
+ closedir (dir);
+ return;
+ }
+ device.vendor = vendor;
+ device.product = product;
+ device.method = sanei_usb_method_scanner_driver;
+ DBG (4,
+ "sanei_usb_init: found kernel scanner device (0x%04x/0x%04x) at %s\n",
+ vendor, product, devname);
+ store_device(device);
+ }
+ }
+ closedir (dir);
+ }
+
+ /* Check for devices using (old) libusb */
+#ifdef HAVE_LIBUSB
+ DBG (4, "sanei_usb_init: Looking for libusb devices\n");
+ usb_init ();
+#ifdef DBG_LEVEL
+ if (DBG_LEVEL > 4)
+ usb_set_debug (255);
+#endif /* DBG_LEVEL */
+
+ usb_find_busses ();
+ usb_find_devices ();
+
+ /* Check for the matching device */
+ for (bus = usb_get_busses (); bus; bus = bus->next)
+ {
+ for (dev = bus->devices; dev; dev = dev->next)
+ {
+ int interface;
+ SANE_Bool found = SANE_FALSE;
+
+ if (!dev->config)
+ {
+ DBG (1,
+ "sanei_usb_init: device 0x%04x/0x%04x is not configured\n",
+ dev->descriptor.idVendor, dev->descriptor.idProduct);
+ continue;
+ }
+ if (dev->descriptor.idVendor == 0 || dev->descriptor.idProduct == 0)
+ {
+ DBG (5,
+ "sanei_usb_init: device 0x%04x/0x%04x looks like a root hub\n",
+ dev->descriptor.idVendor, dev->descriptor.idProduct);
+ continue;
+ }
+
+ for (interface = 0;
+ interface < dev->config[0].bNumInterfaces && !found;
+ interface++)
+ {
+ switch (dev->descriptor.bDeviceClass)
+ {
+ case USB_CLASS_VENDOR_SPEC:
+ found = SANE_TRUE;
+ break;
+ case USB_CLASS_PER_INTERFACE:
+ if (dev->config[0].interface[interface].num_altsetting == 0 ||
+ !dev->config[0].interface[interface].altsetting)
+ {
+ DBG (1, "sanei_usb_init: device 0x%04x/0x%04x doesn't "
+ "have an altsetting for interface %d\n",
+ dev->descriptor.idVendor, dev->descriptor.idProduct,
+ interface);
+ continue;
+ }
+ switch (dev->config[0].interface[interface].altsetting[0].
+ bInterfaceClass)
+ {
+ case USB_CLASS_VENDOR_SPEC:
+ case USB_CLASS_PER_INTERFACE:
+ case 6: /* imaging? */
+ case 16: /* data? */
+ found = SANE_TRUE;
+ break;
+ }
+ break;
+ }
+ if (!found)
+ DBG (5,
+ "sanei_usb_init: device 0x%04x/0x%04x, interface %d "
+ "doesn't look like a "
+ "scanner (%d/%d)\n", dev->descriptor.idVendor,
+ dev->descriptor.idProduct, interface,
+ dev->descriptor.bDeviceClass,
+ dev->config[0].interface[interface].altsetting != 0
+ ? dev->config[0].interface[interface].altsetting[0].
+ bInterfaceClass : -1);
+ }
+ interface--;
+ if (!found)
+ {
+ DBG (5,
+ "sanei_usb_init: device 0x%04x/0x%04x: no suitable interfaces\n",
+ dev->descriptor.idVendor, dev->descriptor.idProduct);
+ continue;
+ }
+
+ memset (&device, 0, sizeof (device));
+ device.libusb_device = dev;
+ snprintf (devname, sizeof (devname), "libusb:%s:%s",
+ dev->bus->dirname, dev->filename);
+ device.devname = strdup (devname);
+ if (!device.devname)
+ return;
+ device.vendor = dev->descriptor.idVendor;
+ device.product = dev->descriptor.idProduct;
+ device.method = sanei_usb_method_libusb;
+ device.interface_nr = interface;
+ DBG (4,
+ "sanei_usb_init: found libusb device (0x%04x/0x%04x) interface "
+ "%d at %s\n",
+ dev->descriptor.idVendor, dev->descriptor.idProduct, interface,
+ devname);
+ store_device(device);
+ }
+ }
+#endif /* HAVE_LIBUSB */
+
+
+ /* Check for devices using libusb-1.0 */
+#ifdef HAVE_LIBUSB_1_0
+ if (!sanei_usb_ctx)
+ {
+ DBG (4, "sanei_usb_init: initializing libusb-1.0\n");
+ ret = libusb_init (&sanei_usb_ctx);
+ if (ret < 0)
+ {
+ DBG (1,
+ "sanei_usb_init: failed to initialize libusb-1.0, error %d\n",
+ ret);
+
+ goto failed_libusb_1_0;
+ }
+#ifdef DBG_LEVEL
+ if (DBG_LEVEL > 4)
+ libusb_set_debug (sanei_usb_ctx, 3);
+#endif /* DBG_LEVEL */
+ }
+
+ DBG (4, "sanei_usb_init: Looking for libusb-1.0 devices\n");
+
+ ndev = libusb_get_device_list (sanei_usb_ctx, &devlist);
+ if (ndev < 0)
+ {
+ DBG (1,
+ "sanei_usb_init: failed to get libusb-1.0 device list, error %d\n",
+ (int) ndev);
+
+ goto failed_libusb_1_0;
+ }
+
+ for (i = 0; i < ndev; i++)
+ {
+ SANE_Bool found = SANE_FALSE;
+
+ dev = devlist[i];
+
+ busno = libusb_get_bus_number (dev);
+ address = libusb_get_device_address (dev);
+
+ ret = libusb_get_device_descriptor (dev, &desc);
+ if (ret < 0)
+ {
+ DBG (1,
+ "sanei_usb_init: could not get device descriptor for device at %03d:%03d (err %d)\n",
+ busno, address, ret);
+ continue;
+ }
+
+ vid = desc.idVendor;
+ pid = desc.idProduct;
+
+ if ((vid == 0) || (pid == 0))
+ {
+ DBG (5,
+ "sanei_usb_init: device 0x%04x/0x%04x at %03d:%03d looks like a root hub\n",
+ vid, pid, busno, address);
+ continue;
+ }
+
+ ret = libusb_open (dev, &hdl);
+ if (ret < 0)
+ {
+ DBG (1,
+ "sanei_usb_init: skipping device 0x%04x/0x%04x at %03d:%03d: cannot open: %s\n",
+ vid, pid, busno, address, sanei_libusb_strerror (ret));
+
+ continue;
+ }
+
+ ret = libusb_get_configuration (hdl, &config);
+
+ libusb_close (hdl);
+
+ if (ret < 0)
+ {
+ DBG (1,
+ "sanei_usb_init: could not get configuration for device 0x%04x/0x%04x at %03d:%03d (err %d)\n",
+ vid, pid, busno, address, ret);
+ continue;
+ }
+
+ if (config == 0)
+ {
+ DBG (1,
+ "sanei_usb_init: device 0x%04x/0x%04x at %03d:%03d is not configured\n",
+ vid, pid, busno, address);
+ continue;
+ }
+
+ ret = libusb_get_config_descriptor (dev, 0, &config0);
+ if (ret < 0)
+ {
+ DBG (1,
+ "sanei_usb_init: could not get config[0] descriptor for device 0x%04x/0x%04x at %03d:%03d (err %d)\n",
+ vid, pid, busno, address, ret);
+ continue;
+ }
+
+ for (interface = 0; (interface < config0->bNumInterfaces) && !found; interface++)
+ {
+ switch (desc.bDeviceClass)
+ {
+ case LIBUSB_CLASS_VENDOR_SPEC:
+ found = SANE_TRUE;
+ break;
+
+ case LIBUSB_CLASS_PER_INTERFACE:
+ if ((config0->interface[interface].num_altsetting == 0)
+ || !config0->interface[interface].altsetting)
+ {
+ DBG (1, "sanei_usb_init: device 0x%04x/0x%04x doesn't "
+ "have an altsetting for interface %d\n",
+ vid, pid, interface);
+ continue;
+ }
+
+ switch (config0->interface[interface].altsetting[0].bInterfaceClass)
+ {
+ case LIBUSB_CLASS_VENDOR_SPEC:
+ case LIBUSB_CLASS_PER_INTERFACE:
+ case LIBUSB_CLASS_PTP:
+ case 16: /* data? */
+ found = SANE_TRUE;
+ break;
+ }
+ break;
+ }
+
+ if (!found)
+ DBG (5,
+ "sanei_usb_init: device 0x%04x/0x%04x, interface %d "
+ "doesn't look like a scanner (%d/%d)\n",
+ vid, pid, interface, desc.bDeviceClass,
+ (config0->interface[interface].altsetting != 0)
+ ? config0->interface[interface].altsetting[0].bInterfaceClass : -1);
+ }
+
+ libusb_free_config_descriptor (config0);
+
+ interface--;
+
+ if (!found)
+ {
+ DBG (5,
+ "sanei_usb_init: device 0x%04x/0x%04x at %03d:%03d: no suitable interfaces\n",
+ vid, pid, busno, address);
+ continue;
+ }
+
+ memset (&device, 0, sizeof (device));
+ device.lu_device = libusb_ref_device(dev);
+ snprintf (devname, sizeof (devname), "libusb:%03d:%03d",
+ busno, address);
+ device.devname = strdup (devname);
+ if (!device.devname)
+ return;
+ device.vendor = vid;
+ device.product = pid;
+ device.method = sanei_usb_method_libusb;
+ device.interface_nr = interface;
+ DBG (4,
+ "sanei_usb_init: found libusb-1.0 device (0x%04x/0x%04x) interface "
+ "%d at %s\n",
+ vid, pid, interface, devname);
+
+ store_device (device);
+ }
+
+ libusb_free_device_list (devlist, 1);
+
+ failed_libusb_1_0:
+ /* libusb 1.0 failed to initialize */
+
+#endif /* HAVE_LIBUSB_1_0 */
+
+#if !defined(HAVE_LIBUSB) && !defined(HAVE_LIBUSB_1_0)
+ DBG (4, "sanei_usb_init: SANE is built without support for libusb\n");
+#endif
+
+
+#ifdef HAVE_USBCALLS
+ /* Check for devices using OS/2 USBCALLS Interface */
+
+ CHAR ucData[2048];
+ struct usb_device_descriptor *pDevDesc;
+ struct usb_config_descriptor *pCfgDesc;
+ struct usb_interface_descriptor *intf;
+ struct usb_endpoint_descriptor *ep;
+ struct usb_descriptor_header *pDescHead;
+
+ APIRET rc;
+ ULONG ulNumDev, ulDev, ulBufLen;
+
+ ulBufLen = sizeof(ucData);
+ memset(&ucData,0,sizeof(ucData));
+ rc = UsbQueryNumberDevices( &ulNumDev);
+
+ if(rc==0 && ulNumDev)
+ {
+ for (ulDev=1; ulDev<=ulNumDev; ulDev++)
+ {
+ rc = UsbQueryDeviceReport( ulDev,
+ &ulBufLen,
+ ucData);
+
+ pDevDesc = (struct usb_device_descriptor*)ucData;
+ pCfgDesc = (struct usb_config_descriptor*) (ucData+sizeof(struct usb_device_descriptor));
+ int interface=0;
+ SANE_Bool found;
+ if (!pCfgDesc->bConfigurationValue)
+ {
+ DBG (1, "sanei_usb_init: device 0x%04x/0x%04x is not configured\n",
+ pDevDesc->idVendor, pDevDesc->idProduct);
+ continue;
+ }
+ if (pDevDesc->idVendor == 0 || pDevDesc->idProduct == 0)
+ {
+ DBG (5, "sanei_usb_init: device 0x%04x/0x%04x looks like a root hub\n",
+ pDevDesc->idVendor, pDevDesc->idProduct);
+ continue;
+ }
+ found = SANE_FALSE;
+
+ if (pDevDesc->bDeviceClass == USB_CLASS_VENDOR_SPEC)
+ {
+ found = SANE_TRUE;
+ }
+
+ if (!found)
+ {
+ DBG (5, "sanei_usb_init: device 0x%04x/0x%04x: no suitable interfaces\n",
+ pDevDesc->idVendor, pDevDesc->idProduct);
+ continue;
+ }
+
+ snprintf (devname, sizeof (devname), "usbcalls:%d",
+ ulDev);
+ memset (&device, 0, sizeof (device));
+ device.devname = strdup (devname);
+ device.fd = ulDev; /* store usbcalls device number */
+ device.vendor = pDevDesc->idVendor;
+ device.product = pDevDesc->idProduct;
+ device.method = sanei_usb_method_usbcalls;
+ device.interface_nr = interface;
+ DBG (4, "sanei_usb_init: found usbcalls device (0x%04x/0x%04x) as device number %s\n",
+ pDevDesc->idVendor, pDevDesc->idProduct,device.devname);
+ store_device(device);
+ }
+ }
+
+#endif /* HAVE_USBCALLS */
+
+ DBG (5, "sanei_usb_init: found %d devices\n", device_number);
+ if (debug_level > 5)
+ {
+ for (i = 0; i < device_number; i++)
+ {
+ if(devices[i].missing)
+ continue;
+ DBG (6, "sanei_usb_init: device %02d is %s\n", i, devices[i].devname);
+ }
+ }
+}
+
+
+/* This logically belongs to sanei_config.c but not every backend that
+ uses sanei_config() wants to depend on sanei_usb. */
+void
+sanei_usb_attach_matching_devices (const char *name,
+ SANE_Status (*attach) (const char *dev))
+{
+ char *vendor, *product;
+
+ if (strncmp (name, "usb", 3) == 0)
+ {
+ SANE_Word vendorID = 0, productID = 0;
+
+ name += 3;
+
+ name = sanei_config_skip_whitespace (name);
+ if (*name)
+ {
+ name = sanei_config_get_string (name, &vendor);
+ if (vendor)
+ {
+ vendorID = strtol (vendor, 0, 0);
+ free (vendor);
+ }
+ name = sanei_config_skip_whitespace (name);
+ }
+
+ name = sanei_config_skip_whitespace (name);
+ if (*name)
+ {
+ name = sanei_config_get_string (name, &product);
+ if (product)
+ {
+ productID = strtol (product, 0, 0);
+ free (product);
+ }
+ }
+ sanei_usb_find_devices (vendorID, productID, attach);
+ }
+ else
+ (*attach) (name);
+}
+
+SANE_Status
+sanei_usb_get_vendor_product_byname (SANE_String_Const devname,
+ SANE_Word * vendor, SANE_Word * product)
+{
+ int i;
+ SANE_Bool found = SANE_FALSE;
+
+ for (i = 0; i < device_number && devices[i].devname; i++)
+ {
+ if(devices[i].missing)
+ continue;
+
+ if (strcmp (devices[i].devname, devname) == 0)
+ {
+ found = SANE_TRUE;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ DBG (1, "sanei_usb_get_vendor_product_byname: can't find device `%s' in list\n", devname);
+ return SANE_STATUS_INVAL;
+ }
+
+ if ((devices[i].vendor == 0) && (devices[i].product == 0))
+ {
+ DBG (1, "sanei_usb_get_vendor_product_byname: not support for this method\n");
+ return SANE_STATUS_UNSUPPORTED;
+ }
+
+ if (vendor)
+ *vendor = devices[i].vendor;
+
+ if (product)
+ *product = devices[i].product;
+
+ return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+sanei_usb_get_vendor_product (SANE_Int dn, SANE_Word * vendor,
+ SANE_Word * product)
+{
+ SANE_Word vendorID = 0;
+ SANE_Word productID = 0;
+
+ if (dn >= device_number || dn < 0)
+ {
+ DBG (1, "sanei_usb_get_vendor_product: dn >= device number || dn < 0\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ if (devices[dn].method == sanei_usb_method_scanner_driver)
+ kernel_get_vendor_product (devices[dn].fd, devices[dn].devname, &vendorID, &productID);
+ else if (devices[dn].method == sanei_usb_method_libusb)
+ {
+#ifdef HAVE_LIBUSB
+ vendorID = usb_device (devices[dn].libusb_handle)->descriptor.idVendor;
+ productID =
+ usb_device (devices[dn].libusb_handle)->descriptor.idProduct;
+#elif defined(HAVE_LIBUSB_1_0)
+ vendorID = devices[dn].vendor;
+ productID = devices[dn].product;
+#else
+ DBG (1, "sanei_usb_get_vendor_product: libusb support missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+#endif /* HAVE_LIBUSB */
+ }
+ else if (devices[dn].method == sanei_usb_method_usbcalls)
+ {
+#ifdef HAVE_USBCALLS
+ vendorID = devices[dn].vendor;
+ productID = devices[dn].product;
+#else
+ DBG (1, "sanei_usb_get_vendor_product: usbcalls support missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+#endif /* HAVE_USBCALLS */
+ }
+ else
+ {
+ DBG (1, "sanei_usb_get_vendor_product: access method %d not "
+ "implemented\n", devices[dn].method);
+ return SANE_STATUS_INVAL;
+ }
+ if (vendor)
+ *vendor = vendorID;
+ if (product)
+ *product = productID;
+
+ if (!vendorID || !productID)
+ {
+ DBG (3, "sanei_usb_get_vendor_product: device %d: Your OS doesn't "
+ "seem to support detection of vendor+product ids\n", dn);
+ return SANE_STATUS_UNSUPPORTED;
+ }
+ else
+ {
+ DBG (3, "sanei_usb_get_vendor_product: device %d: vendorID: 0x%04x, "
+ "productID: 0x%04x\n", dn, vendorID, productID);
+ return SANE_STATUS_GOOD;
+ }
+}
+
+SANE_Status
+sanei_usb_find_devices (SANE_Int vendor, SANE_Int product,
+ SANE_Status (*attach) (SANE_String_Const dev))
+{
+ SANE_Int dn = 0;
+
+ DBG (3,
+ "sanei_usb_find_devices: vendor=0x%04x, product=0x%04x\n",
+ vendor, product);
+
+ while (devices[dn].devname && dn < device_number)
+ {
+ if (devices[dn].vendor == vendor
+ && devices[dn].product == product
+ && !devices[dn].missing
+ && attach)
+ attach (devices[dn].devname);
+ dn++;
+ }
+ return SANE_STATUS_GOOD;
+}
+
+void
+sanei_usb_set_endpoint (SANE_Int dn, SANE_Int ep_type, SANE_Int ep)
+{
+ if (dn >= device_number || dn < 0)
+ {
+ DBG (1, "sanei_usb_set_endpoint: dn >= device number || dn < 0\n");
+ return;
+ }
+
+ DBG (5, "sanei_usb_set_endpoint: Setting endpoint of type 0x%02x to 0x%02x\n", ep_type, ep);
+ switch (ep_type)
+ {
+ case USB_DIR_IN|USB_ENDPOINT_TYPE_BULK:
+ devices[dn].bulk_in_ep = ep;
+ break;
+ case USB_DIR_OUT|USB_ENDPOINT_TYPE_BULK:
+ devices[dn].bulk_out_ep = ep;
+ break;
+ case USB_DIR_IN|USB_ENDPOINT_TYPE_ISOCHRONOUS:
+ devices[dn].iso_in_ep = ep;
+ break;
+ case USB_DIR_OUT|USB_ENDPOINT_TYPE_ISOCHRONOUS:
+ devices[dn].iso_out_ep = ep;
+ break;
+ case USB_DIR_IN|USB_ENDPOINT_TYPE_INTERRUPT:
+ devices[dn].int_in_ep = ep;
+ break;
+ case USB_DIR_OUT|USB_ENDPOINT_TYPE_INTERRUPT:
+ devices[dn].int_out_ep = ep;
+ break;
+ case USB_DIR_IN|USB_ENDPOINT_TYPE_CONTROL:
+ devices[dn].control_in_ep = ep;
+ break;
+ case USB_DIR_OUT|USB_ENDPOINT_TYPE_CONTROL:
+ devices[dn].control_out_ep = ep;
+ break;
+ }
+}
+
+SANE_Int
+sanei_usb_get_endpoint (SANE_Int dn, SANE_Int ep_type)
+{
+ if (dn >= device_number || dn < 0)
+ {
+ DBG (1, "sanei_usb_get_endpoint: dn >= device number || dn < 0\n");
+ return 0;
+ }
+
+ switch (ep_type)
+ {
+ case USB_DIR_IN|USB_ENDPOINT_TYPE_BULK:
+ return devices[dn].bulk_in_ep;
+ case USB_DIR_OUT|USB_ENDPOINT_TYPE_BULK:
+ return devices[dn].bulk_out_ep;
+ case USB_DIR_IN|USB_ENDPOINT_TYPE_ISOCHRONOUS:
+ return devices[dn].iso_in_ep;
+ case USB_DIR_OUT|USB_ENDPOINT_TYPE_ISOCHRONOUS:
+ return devices[dn].iso_out_ep;
+ case USB_DIR_IN|USB_ENDPOINT_TYPE_INTERRUPT:
+ return devices[dn].int_in_ep;
+ case USB_DIR_OUT|USB_ENDPOINT_TYPE_INTERRUPT:
+ return devices[dn].int_out_ep;
+ case USB_DIR_IN|USB_ENDPOINT_TYPE_CONTROL:
+ return devices[dn].control_in_ep;
+ case USB_DIR_OUT|USB_ENDPOINT_TYPE_CONTROL:
+ return devices[dn].control_out_ep;
+ default:
+ return 0;
+ }
+}
+
+SANE_Status
+sanei_usb_open (SANE_String_Const devname, SANE_Int * dn)
+{
+ int devcount;
+ SANE_Bool found = SANE_FALSE;
+ int c, i, a;
+
+ DBG (5, "sanei_usb_open: trying to open device `%s'\n", devname);
+ if (!dn)
+ {
+ DBG (1, "sanei_usb_open: can't open `%s': dn == NULL\n", devname);
+ return SANE_STATUS_INVAL;
+ }
+
+ for (devcount = 0;
+ devcount < device_number && devices[devcount].devname != 0;
+ devcount++)
+ {
+ if(devices[devcount].missing)
+ continue;
+
+ if (strcmp (devices[devcount].devname, devname) == 0)
+ {
+ if (devices[devcount].open)
+ {
+ DBG (1, "sanei_usb_open: device `%s' already open\n", devname);
+ return SANE_STATUS_INVAL;
+ }
+ found = SANE_TRUE;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ DBG (1, "sanei_usb_open: can't find device `%s' in list\n", devname);
+ return SANE_STATUS_INVAL;
+ }
+
+ if (devices[devcount].method == sanei_usb_method_libusb)
+ {
+#ifdef HAVE_LIBUSB
+ struct usb_device *dev;
+ struct usb_interface_descriptor *interface;
+ int result, num;
+
+ devices[devcount].libusb_handle =
+ usb_open (devices[devcount].libusb_device);
+ if (!devices[devcount].libusb_handle)
+ {
+ SANE_Status status = SANE_STATUS_INVAL;
+
+ DBG (1, "sanei_usb_open: can't open device `%s': %s\n",
+ devname, strerror (errno));
+ if (errno == EPERM)
+ {
+ DBG (1, "Make sure you run as root or set appropriate "
+ "permissions\n");
+ status = SANE_STATUS_ACCESS_DENIED;
+ }
+ else if (errno == EBUSY)
+ {
+ DBG (1, "Maybe the kernel scanner driver claims the "
+ "scanner's interface?\n");
+ status = SANE_STATUS_DEVICE_BUSY;
+ }
+ return status;
+ }
+
+ dev = usb_device (devices[devcount].libusb_handle);
+
+#if defined (__linux__)
+ if (!getenv ("SANEI_USB_KEEP_USBLP"))
+ {
+ memset (devices[devcount].driver_detached, 0, MAX_INTERFACE_FLAGS);
+ detach_usblp_driver (devices[devcount].libusb_handle,
+ devices[devcount].driver_detached);
+ }
+#endif /* __linux__ */
+
+ /* Set the configuration */
+ if (!dev->config)
+ {
+ DBG (1, "sanei_usb_open: device `%s' not configured?\n", devname);
+ return SANE_STATUS_INVAL;
+ }
+ if (dev->descriptor.bNumConfigurations > 1)
+ {
+ DBG (3, "sanei_usb_open: more than one "
+ "configuration (%d), choosing first config (%d)\n",
+ dev->descriptor.bNumConfigurations,
+ dev->config[0].bConfigurationValue);
+
+ result = usb_set_configuration (devices[devcount].libusb_handle,
+ dev->config[0].bConfigurationValue);
+ if (result < 0)
+ {
+ SANE_Status status = SANE_STATUS_INVAL;
+
+ DBG (1, "sanei_usb_open: libusb complained: %s\n",
+ usb_strerror ());
+ if (errno == EPERM)
+ {
+ DBG (1, "Make sure you run as root or set appropriate "
+ "permissions\n");
+ status = SANE_STATUS_ACCESS_DENIED;
+ }
+ else if (errno == EBUSY)
+ {
+ DBG (3, "Maybe the kernel scanner driver or usblp claims "
+ "the interface? Ignoring this error...\n");
+ status = SANE_STATUS_GOOD;
+ }
+ if (status != SANE_STATUS_GOOD)
+ {
+ usb_close (devices[devcount].libusb_handle);
+ return status;
+ }
+ }
+ }
+
+ /* Claim the interface */
+ result = usb_claim_interface (devices[devcount].libusb_handle,
+ devices[devcount].interface_nr);
+ if (result < 0)
+ {
+ SANE_Status status = SANE_STATUS_INVAL;
+
+ DBG (1, "sanei_usb_open: libusb complained: %s\n", usb_strerror ());
+ if (errno == EPERM)
+ {
+ DBG (1, "Make sure you run as root or set appropriate "
+ "permissions\n");
+ status = SANE_STATUS_ACCESS_DENIED;
+ }
+ else if (errno == EBUSY)
+ {
+ DBG (1, "Maybe the kernel scanner driver claims the "
+ "scanner's interface?\n");
+ status = SANE_STATUS_DEVICE_BUSY;
+ }
+ usb_close (devices[devcount].libusb_handle);
+ return status;
+ }
+
+ /* Loop through all of the configurations */
+ for (c = 0; c < dev->descriptor.bNumConfigurations; c++)
+ {
+ /* Loop through all of the interfaces */
+ for (i = 0; i < dev->config[c].bNumInterfaces; i++)
+ {
+ /* Loop through all of the alternate settings */
+ for (a = 0; a < dev->config[c].interface[i].num_altsetting; a++)
+ {
+ DBG (5, "sanei_usb_open: configuration nr: %d\n", c);
+ DBG (5, "sanei_usb_open: interface nr: %d\n", i);
+ DBG (5, "sanei_usb_open: alt_setting nr: %d\n", a);
+
+ /* Start by interfaces found in sanei_usb_init */
+ if (c == 0 && i != devices[devcount].interface_nr)
+ {
+ DBG (5, "sanei_usb_open: interface %d not detected as "
+ "a scanner by sanei_usb_init, ignoring.\n", i);
+ continue;
+ }
+
+ interface = &dev->config[c].interface[i].altsetting[a];
+
+ /* Now we look for usable endpoints */
+ for (num = 0; num < interface->bNumEndpoints; num++)
+ {
+ struct usb_endpoint_descriptor *endpoint;
+ int address, direction, transfer_type;
+
+ endpoint = &interface->endpoint[num];
+ DBG (5, "sanei_usb_open: endpoint nr: %d\n", num);
+ transfer_type =
+ endpoint->bmAttributes & USB_ENDPOINT_TYPE_MASK;
+ address =
+ endpoint->
+ bEndpointAddress & USB_ENDPOINT_ADDRESS_MASK;
+ direction =
+ endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK;
+
+ DBG (5, "sanei_usb_open: direction: %d\n", direction);
+ transfer_type =
+ endpoint->bmAttributes & USB_ENDPOINT_TYPE_MASK;
+
+ DBG (5,
+ "sanei_usb_open: address: %d transfertype: %d\n",
+ address, transfer_type);
+
+
+ /* save the endpoints we need later */
+ if (transfer_type == USB_ENDPOINT_TYPE_INTERRUPT)
+ {
+ DBG (5,
+ "sanei_usb_open: found interrupt-%s endpoint (address 0x%02x)\n",
+ direction ? "in" : "out", address);
+ if (direction) /* in */
+ {
+ if (devices[devcount].int_in_ep)
+ DBG (3,
+ "sanei_usb_open: we already have a int-in endpoint "
+ "(address: 0x%02x), ignoring the new one\n",
+ devices[devcount].int_in_ep);
+ else
+ devices[devcount].int_in_ep =
+ endpoint->bEndpointAddress;
+ }
+ else
+ {
+ if (devices[devcount].int_out_ep)
+ DBG (3,
+ "sanei_usb_open: we already have a int-out endpoint "
+ "(address: 0x%02x), ignoring the new one\n",
+ devices[devcount].int_out_ep);
+ else
+ devices[devcount].int_out_ep =
+ endpoint->bEndpointAddress;
+ }
+ }
+ else if (transfer_type == USB_ENDPOINT_TYPE_BULK)
+ {
+ DBG (5,
+ "sanei_usb_open: found bulk-%s endpoint (address 0x%02x)\n",
+ direction ? "in" : "out", address);
+ if (direction) /* in */
+ {
+ if (devices[devcount].bulk_in_ep)
+ DBG (3,
+ "sanei_usb_open: we already have a bulk-in endpoint "
+ "(address: 0x%02x), ignoring the new one\n",
+ devices[devcount].bulk_in_ep);
+ else
+ devices[devcount].bulk_in_ep =
+ endpoint->bEndpointAddress;
+ }
+ else
+ {
+ if (devices[devcount].bulk_out_ep)
+ DBG (3,
+ "sanei_usb_open: we already have a bulk-out endpoint "
+ "(address: 0x%02x), ignoring the new one\n",
+ devices[devcount].bulk_out_ep);
+ else
+ devices[devcount].bulk_out_ep =
+ endpoint->bEndpointAddress;
+ }
+ }
+ else if (transfer_type == USB_ENDPOINT_TYPE_ISOCHRONOUS)
+ {
+ DBG (5,
+ "sanei_usb_open: found isochronous-%s endpoint (address 0x%02x)\n",
+ direction ? "in" : "out", address);
+ if (direction) /* in */
+ {
+ if (devices[devcount].iso_in_ep)
+ DBG (3,
+ "sanei_usb_open: we already have a isochronous-in endpoint "
+ "(address: 0x%02x), ignoring the new one\n",
+ devices[devcount].iso_in_ep);
+ else
+ devices[devcount].iso_in_ep =
+ endpoint->bEndpointAddress;
+ }
+ else
+ {
+ if (devices[devcount].iso_out_ep)
+ DBG (3,
+ "sanei_usb_open: we already have a isochronous-out endpoint "
+ "(address: 0x%02x), ignoring the new one\n",
+ devices[devcount].iso_out_ep);
+ else
+ devices[devcount].iso_out_ep =
+ endpoint->bEndpointAddress;
+ }
+ }
+ else if (transfer_type == USB_ENDPOINT_TYPE_CONTROL)
+ {
+ DBG (5,
+ "sanei_usb_open: found control-%s endpoint (address 0x%02x)\n",
+ direction ? "in" : "out", address);
+ if (direction) /* in */
+ {
+ if (devices[devcount].control_in_ep)
+ DBG (3,
+ "sanei_usb_open: we already have a control-in endpoint "
+ "(address: 0x%02x), ignoring the new one\n",
+ devices[devcount].control_in_ep);
+ else
+ devices[devcount].control_in_ep =
+ endpoint->bEndpointAddress;
+ }
+ else
+ {
+ if (devices[devcount].control_out_ep)
+ DBG (3,
+ "sanei_usb_open: we already have a control-out endpoint "
+ "(address: 0x%02x), ignoring the new one\n",
+ devices[devcount].control_out_ep);
+ else
+ devices[devcount].control_out_ep =
+ endpoint->bEndpointAddress;
+ }
+ }
+ }
+ }
+ }
+ }
+
+#elif defined(HAVE_LIBUSB_1_0) /* libusb-1.0 */
+
+ int config;
+ libusb_device *dev;
+ struct libusb_device_descriptor desc;
+ struct libusb_config_descriptor *config0;
+ int result, num;
+
+ dev = devices[devcount].lu_device;
+
+ result = libusb_open (dev, &devices[devcount].lu_handle);
+ if (result < 0)
+ {
+ SANE_Status status = SANE_STATUS_INVAL;
+
+ DBG (1, "sanei_usb_open: can't open device `%s': %s\n",
+ devname, sanei_libusb_strerror (result));
+ if (result == LIBUSB_ERROR_ACCESS)
+ {
+ DBG (1, "Make sure you run as root or set appropriate "
+ "permissions\n");
+ status = SANE_STATUS_ACCESS_DENIED;
+ }
+ else if (result == LIBUSB_ERROR_BUSY)
+ {
+ DBG (1, "Maybe the kernel scanner driver claims the "
+ "scanner's interface?\n");
+ status = SANE_STATUS_DEVICE_BUSY;
+ }
+ else if (result == LIBUSB_ERROR_NO_MEM)
+ {
+ status = SANE_STATUS_NO_MEM;
+ }
+ return status;
+ }
+
+#if defined (__linux__)
+ if (!getenv ("SANEI_USB_KEEP_USBLP"))
+ {
+ memset (devices[devcount].driver_detached, 0, MAX_INTERFACE_FLAGS);
+ detach_usblp_driver (devices[devcount].lu_handle,
+ devices[devcount].driver_detached);
+ }
+#endif /* __linux__ */
+
+ result = libusb_get_configuration (devices[devcount].lu_handle, &config);
+ if (result < 0)
+ {
+ DBG (1,
+ "sanei_usb_open: could not get configuration for device `%s' (err %d)\n",
+ devname, result);
+ return SANE_STATUS_INVAL;
+ }
+
+ if (config == 0)
+ {
+ DBG (1, "sanei_usb_open: device `%s' not configured?\n", devname);
+ return SANE_STATUS_INVAL;
+ }
+
+ result = libusb_get_device_descriptor (dev, &desc);
+ if (result < 0)
+ {
+ DBG (1,
+ "sanei_usb_open: could not get device descriptor for device `%s' (err %d)\n",
+ devname, result);
+ return SANE_STATUS_INVAL;
+ }
+
+ result = libusb_get_config_descriptor (dev, 0, &config0);
+ if (result < 0)
+ {
+ DBG (1,
+ "sanei_usb_open: could not get config[0] descriptor for device `%s' (err %d)\n",
+ devname, result);
+ return SANE_STATUS_INVAL;
+ }
+
+ /* Set the configuration */
+ if (desc.bNumConfigurations > 1)
+ {
+ DBG (3, "sanei_usb_open: more than one "
+ "configuration (%d), choosing first config (%d)\n",
+ desc.bNumConfigurations,
+ config0->bConfigurationValue);
+
+ result = 0;
+ if (config != config0->bConfigurationValue)
+ result = libusb_set_configuration (devices[devcount].lu_handle,
+ config0->bConfigurationValue);
+
+ if (result < 0)
+ {
+ SANE_Status status = SANE_STATUS_INVAL;
+
+ DBG (1, "sanei_usb_open: libusb complained: %s\n",
+ sanei_libusb_strerror (result));
+ if (result == LIBUSB_ERROR_ACCESS)
+ {
+ DBG (1, "Make sure you run as root or set appropriate "
+ "permissions\n");
+ status = SANE_STATUS_ACCESS_DENIED;
+ }
+ else if (result == LIBUSB_ERROR_BUSY)
+ {
+ DBG (3, "Maybe the kernel scanner driver or usblp claims "
+ "the interface? Ignoring this error...\n");
+ status = SANE_STATUS_GOOD;
+ }
+
+ if (status != SANE_STATUS_GOOD)
+ {
+ libusb_close (devices[devcount].lu_handle);
+ libusb_free_config_descriptor (config0);
+ return status;
+ }
+ }
+ }
+ libusb_free_config_descriptor (config0);
+
+ /* Claim the interface */
+ result = libusb_claim_interface (devices[devcount].lu_handle,
+ devices[devcount].interface_nr);
+ if (result < 0)
+ {
+ SANE_Status status = SANE_STATUS_INVAL;
+
+ DBG (1, "sanei_usb_open: libusb complained: %s\n",
+ sanei_libusb_strerror (result));
+ if (result == LIBUSB_ERROR_ACCESS)
+ {
+ DBG (1, "Make sure you run as root or set appropriate "
+ "permissions\n");
+ status = SANE_STATUS_ACCESS_DENIED;
+ }
+ else if (result == LIBUSB_ERROR_BUSY)
+ {
+ DBG (1, "Maybe the kernel scanner driver claims the "
+ "scanner's interface?\n");
+ status = SANE_STATUS_DEVICE_BUSY;
+ }
+
+ libusb_close (devices[devcount].lu_handle);
+ return status;
+ }
+
+ /* Loop through all of the configurations */
+ for (c = 0; c < desc.bNumConfigurations; c++)
+ {
+ struct libusb_config_descriptor *config;
+
+ result = libusb_get_config_descriptor (dev, c, &config);
+ if (result < 0)
+ {
+ DBG (1,
+ "sanei_usb_open: could not get config[%d] descriptor for device `%s' (err %d)\n",
+ c, devname, result);
+ continue;
+ }
+
+ /* Loop through all of the interfaces */
+ for (i = 0; i < config->bNumInterfaces; i++)
+ {
+ /* Loop through all of the alternate settings */
+ for (a = 0; a < config->interface[i].num_altsetting; a++)
+ {
+ const struct libusb_interface_descriptor *interface;
+
+ DBG (5, "sanei_usb_open: configuration nr: %d\n", c);
+ DBG (5, "sanei_usb_open: interface nr: %d\n", i);
+ DBG (5, "sanei_usb_open: alt_setting nr: %d\n", a);
+
+ /* Start by interfaces found in sanei_usb_init */
+ if (c == 0 && i != devices[devcount].interface_nr)
+ {
+ DBG (5, "sanei_usb_open: interface %d not detected as "
+ "a scanner by sanei_usb_init, ignoring.\n", i);
+ continue;
+ }
+
+ interface = &config->interface[i].altsetting[a];
+
+ /* Now we look for usable endpoints */
+ for (num = 0; num < interface->bNumEndpoints; num++)
+ {
+ const struct libusb_endpoint_descriptor *endpoint;
+ int address, direction, transfer_type;
+
+ endpoint = &interface->endpoint[num];
+ DBG (5, "sanei_usb_open: endpoint nr: %d\n", num);
+
+ transfer_type = endpoint->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK;
+ address = endpoint->bEndpointAddress & LIBUSB_ENDPOINT_ADDRESS_MASK;
+ direction = endpoint->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK;
+
+ DBG (5, "sanei_usb_open: direction: %d\n", direction);
+ DBG (5, "sanei_usb_open: address: %d transfertype: %d\n",
+ address, transfer_type);
+
+ /* save the endpoints we need later */
+ if (transfer_type == LIBUSB_TRANSFER_TYPE_INTERRUPT)
+ {
+ DBG (5,
+ "sanei_usb_open: found interrupt-%s endpoint (address 0x%02x)\n",
+ direction ? "in" : "out", address);
+ if (direction) /* in */
+ {
+ if (devices[devcount].int_in_ep)
+ DBG (3,
+ "sanei_usb_open: we already have a int-in endpoint "
+ "(address: 0x%02x), ignoring the new one\n",
+ devices[devcount].int_in_ep);
+ else
+ devices[devcount].int_in_ep = endpoint->bEndpointAddress;
+ }
+ else
+ {
+ if (devices[devcount].int_out_ep)
+ DBG (3,
+ "sanei_usb_open: we already have a int-out endpoint "
+ "(address: 0x%02x), ignoring the new one\n",
+ devices[devcount].int_out_ep);
+ else
+ devices[devcount].int_out_ep = endpoint->bEndpointAddress;
+ }
+ }
+ else if (transfer_type == LIBUSB_TRANSFER_TYPE_BULK)
+ {
+ DBG (5,
+ "sanei_usb_open: found bulk-%s endpoint (address 0x%02x)\n",
+ direction ? "in" : "out", address);
+ if (direction) /* in */
+ {
+ if (devices[devcount].bulk_in_ep)
+ DBG (3,
+ "sanei_usb_open: we already have a bulk-in endpoint "
+ "(address: 0x%02x), ignoring the new one\n",
+ devices[devcount].bulk_in_ep);
+ else
+ devices[devcount].bulk_in_ep = endpoint->bEndpointAddress;
+ }
+ else
+ {
+ if (devices[devcount].bulk_out_ep)
+ DBG (3,
+ "sanei_usb_open: we already have a bulk-out endpoint "
+ "(address: 0x%02x), ignoring the new one\n",
+ devices[devcount].bulk_out_ep);
+ else
+ devices[devcount].bulk_out_ep = endpoint->bEndpointAddress;
+ }
+ }
+ else if (transfer_type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS)
+ {
+ DBG (5,
+ "sanei_usb_open: found isochronous-%s endpoint (address 0x%02x)\n",
+ direction ? "in" : "out", address);
+ if (direction) /* in */
+ {
+ if (devices[devcount].iso_in_ep)
+ DBG (3,
+ "sanei_usb_open: we already have a isochronous-in endpoint "
+ "(address: 0x%02x), ignoring the new one\n",
+ devices[devcount].iso_in_ep);
+ else
+ devices[devcount].iso_in_ep = endpoint->bEndpointAddress;
+ }
+ else
+ {
+ if (devices[devcount].iso_out_ep)
+ DBG (3,
+ "sanei_usb_open: we already have a isochronous-out endpoint "
+ "(address: 0x%02x), ignoring the new one\n",
+ devices[devcount].iso_out_ep);
+ else
+ devices[devcount].iso_out_ep = endpoint->bEndpointAddress;
+ }
+ }
+ else if (transfer_type == LIBUSB_TRANSFER_TYPE_CONTROL)
+ {
+ DBG (5,
+ "sanei_usb_open: found control-%s endpoint (address 0x%02x)\n",
+ direction ? "in" : "out", address);
+ if (direction) /* in */
+ {
+ if (devices[devcount].control_in_ep)
+ DBG (3,
+ "sanei_usb_open: we already have a control-in endpoint "
+ "(address: 0x%02x), ignoring the new one\n",
+ devices[devcount].control_in_ep);
+ else
+ devices[devcount].control_in_ep = endpoint->bEndpointAddress;
+ }
+ else
+ {
+ if (devices[devcount].control_out_ep)
+ DBG (3,
+ "sanei_usb_open: we already have a control-out endpoint "
+ "(address: 0x%02x), ignoring the new one\n",
+ devices[devcount].control_out_ep);
+ else
+ devices[devcount].control_out_ep = endpoint->bEndpointAddress;
+ }
+ }
+ }
+ }
+ }
+
+ libusb_free_config_descriptor (config);
+ }
+
+#else /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0 */
+ DBG (1, "sanei_usb_open: can't open device `%s': "
+ "libusb support missing\n", devname);
+ return SANE_STATUS_UNSUPPORTED;
+#endif /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0 */
+ }
+ else if (devices[devcount].method == sanei_usb_method_scanner_driver)
+ {
+ long int flag;
+ /* Using kernel scanner driver */
+ devices[devcount].fd = -1;
+#ifdef HAVE_RESMGR
+ devices[devcount].fd = rsm_open_device (devname, O_RDWR);
+#endif
+ if (devices[devcount].fd == -1)
+ devices[devcount].fd = open (devname, O_RDWR);
+ if (devices[devcount].fd < 0)
+ {
+ SANE_Status status = SANE_STATUS_INVAL;
+
+ if (errno == EACCES)
+ status = SANE_STATUS_ACCESS_DENIED;
+ else if (errno == ENOENT)
+ {
+ DBG (5, "sanei_usb_open: open of `%s' failed: %s\n",
+ devname, strerror (errno));
+ return status;
+ }
+ DBG (1, "sanei_usb_open: open of `%s' failed: %s\n",
+ devname, strerror (errno));
+ return status;
+ }
+ flag = fcntl (devices[devcount].fd, F_GETFD);
+ if (flag >= 0)
+ {
+ if (fcntl (devices[devcount].fd, F_SETFD, flag | FD_CLOEXEC) < 0)
+ DBG (1, "sanei_usb_open: fcntl of `%s' failed: %s\n",
+ devname, strerror (errno));
+ }
+ }
+ else if (devices[devcount].method == sanei_usb_method_usbcalls)
+ {
+#ifdef HAVE_USBCALLS
+ CHAR ucData[2048];
+ struct usb_device_descriptor *pDevDesc;
+ struct usb_config_descriptor *pCfgDesc;
+ struct usb_interface_descriptor *interface;
+ struct usb_endpoint_descriptor *endpoint;
+ struct usb_descriptor_header *pDescHead;
+
+ ULONG ulBufLen;
+ ulBufLen = sizeof(ucData);
+ memset(&ucData,0,sizeof(ucData));
+
+ int result, num,rc;
+ int address, direction, transfer_type;
+
+ DBG (5, "devname = %s, devcount = %d\n",devices[devcount].devname,devcount);
+ DBG (5, "USBCalls device number to open = %d\n",devices[devcount].fd);
+ DBG (5, "USBCalls Vendor/Product to open = 0x%04x/0x%04x\n",
+ devices[devcount].vendor,devices[devcount].product);
+
+ rc = UsbOpen (&dh,
+ devices[devcount].vendor,
+ devices[devcount].product,
+ USB_ANY_PRODUCTVERSION,
+ USB_OPEN_FIRST_UNUSED);
+ DBG (1, "sanei_usb_open: UsbOpen rc = %d\n",rc);
+ if (rc!=0)
+ {
+ SANE_Status status = SANE_STATUS_INVAL;
+ DBG (1, "sanei_usb_open: can't open device `%s': %s\n",
+ devname, strerror (rc));
+ return status;
+ }
+ rc = UsbQueryDeviceReport( devices[devcount].fd,
+ &ulBufLen,
+ ucData);
+ DBG (1, "sanei_usb_open: UsbQueryDeviceReport rc = %d\n",rc);
+ pDevDesc = (struct usb_device_descriptor*)ucData;
+ pCfgDesc = (struct usb_config_descriptor*) (ucData+sizeof(struct usb_device_descriptor));
+ UCHAR *pCurPtr = (UCHAR*) pCfgDesc;
+ UCHAR *pEndPtr = pCurPtr+ pCfgDesc->wTotalLength;
+ pDescHead = (struct usb_descriptor_header *) (pCurPtr+pCfgDesc->bLength);
+ /* Set the configuration */
+ if (pDevDesc->bNumConfigurations > 1)
+ {
+ DBG (3, "sanei_usb_open: more than one "
+ "configuration (%d), choosing first config (%d)\n",
+ pDevDesc->bNumConfigurations,
+ pCfgDesc->bConfigurationValue);
+ }
+ DBG (5, "UsbDeviceSetConfiguration parameters: dh = %p, bConfigurationValue = %d\n",
+ dh,pCfgDesc->bConfigurationValue);
+ result = UsbDeviceSetConfiguration (dh,
+ pCfgDesc->bConfigurationValue);
+ DBG (1, "sanei_usb_open: UsbDeviceSetConfiguration rc = %d\n",result);
+ if (result)
+ {
+ SANE_Status status = SANE_STATUS_INVAL;
+ DBG (1, "sanei_usb_open: usbcalls complained on UsbDeviceSetConfiguration, rc= %d\n", result);
+ status = SANE_STATUS_ACCESS_DENIED;
+ UsbClose (dh);
+ return status;
+ }
+
+ /* Now we look for usable endpoints */
+
+ for (pDescHead = (struct usb_descriptor_header *) (pCurPtr+pCfgDesc->bLength);
+ pDescHead;pDescHead = GetNextDescriptor(pDescHead,pEndPtr) )
+ {
+ switch(pDescHead->bDescriptorType)
+ {
+ case USB_DT_INTERFACE:
+ interface = (struct usb_interface_descriptor *) pDescHead;
+ DBG (5, "Found %d endpoints\n",interface->bNumEndpoints);
+ DBG (5, "bAlternateSetting = %d\n",interface->bAlternateSetting);
+ break;
+ case USB_DT_ENDPOINT:
+ endpoint = (struct usb_endpoint_descriptor*)pDescHead;
+ address = endpoint->bEndpointAddress;
+ direction = endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK;
+ transfer_type = endpoint->bmAttributes & USB_ENDPOINT_TYPE_MASK;
+ /* save the endpoints we need later */
+ if (transfer_type == USB_ENDPOINT_TYPE_INTERRUPT)
+ {
+ DBG (5, "sanei_usb_open: found interupt-%s endpoint (address %2x)\n",
+ direction ? "in" : "out", address);
+ if (direction) /* in */
+ {
+ if (devices[devcount].int_in_ep)
+ DBG (3, "sanei_usb_open: we already have a int-in endpoint "
+ "(address: %d), ignoring the new one\n",
+ devices[devcount].int_in_ep);
+ else
+ devices[devcount].int_in_ep = endpoint->bEndpointAddress;
+ }
+ else
+ if (devices[devcount].int_out_ep)
+ DBG (3, "sanei_usb_open: we already have a int-out endpoint "
+ "(address: %d), ignoring the new one\n",
+ devices[devcount].int_out_ep);
+ else
+ devices[devcount].int_out_ep = endpoint->bEndpointAddress;
+ }
+ else if (transfer_type == USB_ENDPOINT_TYPE_BULK)
+ {
+ DBG (5, "sanei_usb_open: found bulk-%s endpoint (address %2x)\n",
+ direction ? "in" : "out", address);
+ if (direction) /* in */
+ {
+ if (devices[devcount].bulk_in_ep)
+ DBG (3, "sanei_usb_open: we already have a bulk-in endpoint "
+ "(address: %d), ignoring the new one\n",
+ devices[devcount].bulk_in_ep);
+ else
+ devices[devcount].bulk_in_ep = endpoint->bEndpointAddress;
+ }
+ else
+ {
+ if (devices[devcount].bulk_out_ep)
+ DBG (3, "sanei_usb_open: we already have a bulk-out endpoint "
+ "(address: %d), ignoring the new one\n",
+ devices[devcount].bulk_out_ep);
+ else
+ devices[devcount].bulk_out_ep = endpoint->bEndpointAddress;
+ }
+ }
+ /* ignore currently unsupported endpoints */
+ else {
+ DBG (5, "sanei_usb_open: ignoring %s-%s endpoint "
+ "(address: %d)\n",
+ transfer_type == USB_ENDPOINT_TYPE_CONTROL ? "control" :
+ transfer_type == USB_ENDPOINT_TYPE_ISOCHRONOUS
+ ? "isochronous" : "interrupt",
+ direction ? "in" : "out", address);
+ continue;
+ }
+ break;
+ }
+ }
+#else
+ DBG (1, "sanei_usb_open: can't open device `%s': "
+ "usbcalls support missing\n", devname);
+ return SANE_STATUS_UNSUPPORTED;
+#endif /* HAVE_USBCALLS */
+ }
+ else
+ {
+ DBG (1, "sanei_usb_open: access method %d not implemented\n",
+ devices[devcount].method);
+ return SANE_STATUS_INVAL;
+ }
+
+ devices[devcount].open = SANE_TRUE;
+ *dn = devcount;
+ DBG (3, "sanei_usb_open: opened usb device `%s' (*dn=%d)\n",
+ devname, devcount);
+ return SANE_STATUS_GOOD;
+}
+
+void
+sanei_usb_close (SANE_Int dn)
+{
+ DBG (5, "sanei_usb_close: closing device %d\n", dn);
+ if (dn >= device_number || dn < 0)
+ {
+ DBG (1, "sanei_usb_close: dn >= device number || dn < 0\n");
+ return;
+ }
+ if (!devices[dn].open)
+ {
+ DBG (1, "sanei_usb_close: device %d already closed or never opened\n",
+ dn);
+ return;
+ }
+ if (devices[dn].method == sanei_usb_method_scanner_driver)
+ close (devices[dn].fd);
+ else if (devices[dn].method == sanei_usb_method_usbcalls)
+ {
+#ifdef HAVE_USBCALLS
+ int rc;
+ rc=UsbClose (dh);
+ DBG (5,"rc of UsbClose = %d\n",rc);
+#else
+ DBG (1, "sanei_usb_close: usbcalls support missing\n");
+#endif
+ }
+ else
+#ifdef HAVE_LIBUSB
+ {
+#if 0
+ /* Should only be done in case of a stall */
+ usb_clear_halt (devices[dn].libusb_handle, devices[dn].bulk_in_ep);
+ usb_clear_halt (devices[dn].libusb_handle, devices[dn].bulk_out_ep);
+ usb_clear_halt (devices[dn].libusb_handle, devices[dn].iso_in_ep);
+ /* be careful, we don't know if we are in DATA0 stage now */
+ usb_resetep (devices[dn].libusb_handle, devices[dn].bulk_in_ep);
+ usb_resetep (devices[dn].libusb_handle, devices[dn].bulk_out_ep);
+ usb_resetep (devices[dn].libusb_handle, devices[dn].iso_in_ep);
+#endif /* ! __macos_x__ */
+ usb_release_interface (devices[dn].libusb_handle,
+ devices[dn].interface_nr);
+
+#if defined (__linux__)
+ if (!getenv ("SANEI_USB_KEEP_USBLP"))
+ {
+ attach_usblp_driver (devices[dn].libusb_handle,
+ devices[dn].driver_detached);
+ }
+#endif /* __linux__*/
+
+ usb_close (devices[dn].libusb_handle);
+ }
+#elif defined(HAVE_LIBUSB_1_0)
+ {
+#if 0
+ /* Should only be done in case of a stall */
+ libusb_clear_halt (devices[dn].lu_handle, devices[dn].bulk_in_ep);
+ libusb_clear_halt (devices[dn].lu_handle, devices[dn].bulk_out_ep);
+ libusb_clear_halt (devices[dn].lu_handle, devices[dn].iso_in_ep);
+#endif /* ! __macos_x__ */
+ libusb_release_interface (devices[dn].lu_handle,
+ devices[dn].interface_nr);
+
+#if defined (__linux__)
+ if (!getenv ("SANEI_USB_KEEP_USBLP"))
+ {
+ attach_usblp_driver (devices[dn].lu_handle,
+ devices[dn].driver_detached);
+ }
+#endif /* __linux__*/
+
+ libusb_close (devices[dn].lu_handle);
+ }
+#else /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0 */
+ DBG (1, "sanei_usb_close: libusb support missing\n");
+#endif
+ devices[dn].open = SANE_FALSE;
+ return;
+}
+
+void
+sanei_usb_set_timeout (SANE_Int timeout)
+{
+#if defined(HAVE_LIBUSB) || defined(HAVE_LIBUSB_1_0)
+ libusb_timeout = timeout;
+#else
+ DBG (1, "sanei_usb_set_timeout: libusb support missing\n");
+#endif /* HAVE_LIBUSB || HAVE_LIBUSB_1_0 */
+}
+
+SANE_Status
+sanei_usb_clear_halt (SANE_Int dn)
+{
+#ifdef HAVE_LIBUSB
+ int ret;
+
+ if (dn >= device_number || dn < 0)
+ {
+ DBG (1, "sanei_usb_clear_halt: dn >= device number || dn < 0\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ ret = usb_clear_halt (devices[dn].libusb_handle, devices[dn].bulk_in_ep);
+ if (ret){
+ DBG (1, "sanei_usb_clear_halt: BULK_IN ret=%d\n", ret);
+ return SANE_STATUS_INVAL;
+ }
+
+ ret = usb_clear_halt (devices[dn].libusb_handle, devices[dn].bulk_out_ep);
+ if (ret){
+ DBG (1, "sanei_usb_clear_halt: BULK_OUT ret=%d\n", ret);
+ return SANE_STATUS_INVAL;
+ }
+
+ /* be careful, we don't know if we are in DATA0 stage now
+ ret = usb_resetep (devices[dn].libusb_handle, devices[dn].bulk_in_ep);
+ ret = usb_resetep (devices[dn].libusb_handle, devices[dn].bulk_out_ep);
+ */
+#elif defined(HAVE_LIBUSB_1_0)
+ int ret;
+
+ if (dn >= device_number || dn < 0)
+ {
+ DBG (1, "sanei_usb_clear_halt: dn >= device number || dn < 0\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ ret = libusb_clear_halt (devices[dn].lu_handle, devices[dn].bulk_in_ep);
+ if (ret){
+ DBG (1, "sanei_usb_clear_halt: BULK_IN ret=%d\n", ret);
+ return SANE_STATUS_INVAL;
+ }
+
+ ret = libusb_clear_halt (devices[dn].lu_handle, devices[dn].bulk_out_ep);
+ if (ret){
+ DBG (1, "sanei_usb_clear_halt: BULK_OUT ret=%d\n", ret);
+ return SANE_STATUS_INVAL;
+ }
+#else /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0 */
+ DBG (1, "sanei_usb_clear_halt: libusb support missing\n");
+#endif /* HAVE_LIBUSB || HAVE_LIBUSB_1_0 */
+
+ return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+sanei_usb_reset (SANE_Int dn)
+{
+#ifdef HAVE_LIBUSB
+ int ret;
+
+ ret = usb_reset (devices[dn].libusb_handle);
+ if (ret){
+ DBG (1, "sanei_usb_reset: ret=%d\n", ret);
+ return SANE_STATUS_INVAL;
+ }
+
+#elif defined(HAVE_LIBUSB_1_0)
+ int ret;
+
+ ret = libusb_reset_device (devices[dn].lu_handle);
+ if (ret){
+ DBG (1, "sanei_usb_reset: ret=%d\n", ret);
+ return SANE_STATUS_INVAL;
+ }
+
+#else /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0 */
+ DBG (1, "sanei_usb_reset: libusb support missing\n");
+#endif /* HAVE_LIBUSB || HAVE_LIBUSB_1_0 */
+
+ return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+sanei_usb_read_bulk (SANE_Int dn, SANE_Byte * buffer, size_t * size)
+{
+ ssize_t read_size = 0;
+ int tries = 0;
+ const int max_tries = 3;
+ int need_retry = 0;
+
+ if (!size)
+ {
+ DBG (1, "sanei_usb_read_bulk: size == NULL\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ if (dn >= device_number || dn < 0)
+ {
+ DBG (1, "sanei_usb_read_bulk: dn >= device number || dn < 0\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ do
+ {
+ ++tries;
+ DBG (5, "sanei_usb_read_bulk: trying to read %lu bytes\n",
+ (unsigned long) *size);
+
+ if (devices[dn].method == sanei_usb_method_scanner_driver)
+ {
+ read_size = read (devices[dn].fd, buffer, *size);
+
+ if (read_size < 0)
+ DBG (1, "sanei_usb_read_bulk: read failed: %s\n",
+ strerror (errno));
+ }
+ else if (devices[dn].method == sanei_usb_method_libusb)
+#ifdef HAVE_LIBUSB
+ {
+ if (devices[dn].bulk_in_ep)
+ {
+ read_size = usb_bulk_read (devices[dn].libusb_handle,
+ devices[dn].bulk_in_ep, (char *) buffer,
+ (int) *size, libusb_timeout);
+
+ need_retry = errno;
+ if (read_size < 0)
+ {
+ DBG (1, "sanei_usb_read_bulk: read failed: %s\n",
+ strerror (errno));
+ if (-EPIPE == read_size)
+ {
+ usb_clear_halt (devices[dn].libusb_handle,
+ devices[dn].bulk_in_ep);
+ }
+ }
+ }
+ else
+ {
+ DBG (1, "sanei_usb_read_bulk: can't read without a bulk-in "
+ "endpoint\n");
+ return SANE_STATUS_INVAL;
+ }
+ }
+#elif defined(HAVE_LIBUSB_1_0)
+ {
+ if (devices[dn].bulk_in_ep)
+ {
+ int ret;
+ ret = libusb_bulk_transfer (devices[dn].lu_handle,
+ devices[dn].bulk_in_ep, buffer,
+ (int) *size, (int *) &read_size,
+ libusb_timeout);
+
+ if (LIBUSB_ERROR_PIPE == ret)
+ {
+ ret = libusb_clear_halt (devices[dn].lu_handle,
+ devices[dn].bulk_in_ep);
+ }
+ need_retry = ret;
+ if (ret < 0)
+ {
+ DBG (1, "sanei_usb_read_bulk: read failed: %s\n",
+ sanei_libusb_strerror (ret));
+
+ read_size = -1;
+ }
+ }
+ else
+ {
+ DBG (1, "sanei_usb_read_bulk: can't read without a bulk-in "
+ "endpoint\n");
+ return SANE_STATUS_INVAL;
+ }
+ }
+#else /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0 */
+ {
+ DBG (1, "sanei_usb_read_bulk: libusb support missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+ }
+#endif /* not HAVE_LIBUSB */
+ else if (devices[dn].method == sanei_usb_method_usbcalls)
+ {
+#ifdef HAVE_USBCALLS
+ int rc;
+ char* buffer_ptr = (char*) buffer;
+ while (*size)
+ {
+ ULONG ulToRead = (*size>MAX_RW)?MAX_RW:*size;
+ ULONG ulNum = ulToRead;
+ DBG (5, "Entered usbcalls UsbBulkRead with dn = %d\n",dn);
+ DBG (5, "Entered usbcalls UsbBulkRead with dh = %p\n",dh);
+ DBG (5, "Entered usbcalls UsbBulkRead with bulk_in_ep = 0x%02x\n",devices[dn].bulk_in_ep);
+ DBG (5, "Entered usbcalls UsbBulkRead with interface_nr = %d\n",devices[dn].interface_nr);
+ DBG (5, "Entered usbcalls UsbBulkRead with usbcalls_timeout = %d\n",usbcalls_timeout);
+
+ if (devices[dn].bulk_in_ep){
+ rc = UsbBulkRead (dh, devices[dn].bulk_in_ep, devices[dn].interface_nr,
+ &ulToRead, buffer_ptr, usbcalls_timeout);
+ DBG (1, "sanei_usb_read_bulk: rc = %d\n",rc);}
+ else
+ {
+ DBG (1, "sanei_usb_read_bulk: can't read without a bulk-in endpoint\n");
+ return SANE_STATUS_INVAL;
+ }
+ if (rc || (ulNum!=ulToRead)) return SANE_STATUS_INVAL;
+ *size -=ulToRead;
+ buffer_ptr += ulToRead;
+ read_size += ulToRead;
+ }
+#else /* not HAVE_USBCALLS */
+ {
+ DBG (1, "sanei_usb_read_bulk: usbcalls support missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+ }
+#endif /* not HAVE_USBCALLS */
+ }
+ else
+ {
+ DBG (1, "sanei_usb_read_bulk: access method %d not implemented\n",
+ devices[dn].method);
+ return SANE_STATUS_INVAL;
+ }
+ } while (read_size < 0
+#if defined(HAVE_LIBUSB)
+ && (EAGAIN == need_retry || EWOULDBLOCK == need_retry)
+#elif defined(HAVE_LIBUSB_1_0)
+ && LIBUSB_ERROR_BUSY == need_retry
+#endif /* HAVE_LIBUSB_1_0 */
+ && max_tries > tries);
+
+ if (read_size < 0)
+ {
+#ifdef HAVE_LIBUSB
+ if (devices[dn].method == sanei_usb_method_libusb)
+ usb_clear_halt (devices[dn].libusb_handle, devices[dn].bulk_in_ep);
+#elif defined(HAVE_LIBUSB_1_0)
+ if (devices[dn].method == sanei_usb_method_libusb)
+ libusb_clear_halt (devices[dn].lu_handle, devices[dn].bulk_in_ep);
+#endif
+ *size = 0;
+ return SANE_STATUS_IO_ERROR;
+ }
+ if (read_size == 0)
+ {
+ DBG (3, "sanei_usb_read_bulk: read returned EOF\n");
+ *size = 0;
+ return SANE_STATUS_EOF;
+ }
+ if (debug_level > 10)
+ print_buffer (buffer, read_size);
+ DBG (5, "sanei_usb_read_bulk: wanted %lu bytes, got %ld bytes\n",
+ (unsigned long) *size, (unsigned long) read_size);
+ *size = read_size;
+
+ return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+sanei_usb_write_bulk (SANE_Int dn, const SANE_Byte * buffer, size_t * size)
+{
+ ssize_t write_size = 0;
+ int tries = 0;
+ const int max_tries = 3;
+ int need_retry = 0;
+
+ if (!size)
+ {
+ DBG (1, "sanei_usb_write_bulk: size == NULL\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ if (dn >= device_number || dn < 0)
+ {
+ DBG (1, "sanei_usb_write_bulk: dn >= device number || dn < 0\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ do
+ {
+ ++tries;
+ DBG (5, "sanei_usb_write_bulk: trying to write %lu bytes\n",
+ (unsigned long) *size);
+ if (debug_level > 10)
+ print_buffer (buffer, *size);
+
+ if (devices[dn].method == sanei_usb_method_scanner_driver)
+ {
+ write_size = write (devices[dn].fd, buffer, *size);
+
+ if (write_size < 0)
+ DBG (1, "sanei_usb_write_bulk: write failed: %s\n",
+ strerror (errno));
+ }
+ else if (devices[dn].method == sanei_usb_method_libusb)
+#ifdef HAVE_LIBUSB
+ {
+ if (devices[dn].bulk_out_ep)
+ {
+ write_size = usb_bulk_write (devices[dn].libusb_handle,
+ devices[dn].bulk_out_ep,
+ (const char *) buffer,
+ (int) *size, libusb_timeout);
+ need_retry = errno;
+ if (write_size < 0)
+ {
+ DBG (1, "sanei_usb_write_bulk: write failed: %s\n",
+ strerror (errno));
+ if (-EPIPE == write_size)
+ {
+ usb_clear_halt (devices[dn].libusb_handle,
+ devices[dn].bulk_out_ep);
+ }
+ }
+ }
+ else
+ {
+ DBG (1, "sanei_usb_write_bulk: can't write without a bulk-out "
+ "endpoint\n");
+ return SANE_STATUS_INVAL;
+ }
+ }
+#elif defined(HAVE_LIBUSB_1_0)
+ {
+ if (devices[dn].bulk_out_ep)
+ {
+ int ret;
+ int trans_bytes;
+ ret = libusb_bulk_transfer (devices[dn].lu_handle,
+ devices[dn].bulk_out_ep,
+ (SANE_Byte *) buffer,
+ (int) *size, &trans_bytes,
+ libusb_timeout);
+ if (LIBUSB_ERROR_PIPE == ret)
+ {
+ ret = libusb_clear_halt (devices[dn].lu_handle,
+ devices[dn].bulk_out_ep);
+ }
+ need_retry = ret;
+ if (ret < 0)
+ {
+ DBG (1, "sanei_usb_write_bulk: write failed: %s\n",
+ sanei_libusb_strerror (ret));
+
+ write_size = -1;
+ }
+ else
+ write_size = trans_bytes;
+ }
+ else
+ {
+ DBG (1, "sanei_usb_write_bulk: can't write without a bulk-out "
+ "endpoint\n");
+ return SANE_STATUS_INVAL;
+ }
+ }
+#else /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0 */
+ {
+ DBG (1, "sanei_usb_write_bulk: libusb support missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+ }
+#endif /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0 */
+ else if (devices[dn].method == sanei_usb_method_usbcalls)
+ {
+#ifdef HAVE_USBCALLS
+ int rc;
+ DBG (5, "Entered usbcalls UsbBulkWrite with dn = %d\n",dn);
+ DBG (5, "Entered usbcalls UsbBulkWrite with dh = %p\n",dh);
+ DBG (5, "Entered usbcalls UsbBulkWrite with bulk_out_ep = 0x%02x\n",devices[dn].bulk_out_ep);
+ DBG (5, "Entered usbcalls UsbBulkWrite with interface_nr = %d\n",devices[dn].interface_nr);
+ DBG (5, "Entered usbcalls UsbBulkWrite with usbcalls_timeout = %d\n",usbcalls_timeout);
+ while (*size)
+ {
+ ULONG ulToWrite = (*size>MAX_RW)?MAX_RW:*size;
+
+ DBG (5, "size requested to write = %lu, ulToWrite = %lu\n",(unsigned long) *size,ulToWrite);
+ if (devices[dn].bulk_out_ep){
+ rc = UsbBulkWrite (dh, devices[dn].bulk_out_ep, devices[dn].interface_nr,
+ ulToWrite, (char*) buffer, usbcalls_timeout);
+ DBG (1, "sanei_usb_write_bulk: rc = %d\n",rc);
+ }
+ else
+ {
+ DBG (1, "sanei_usb_write_bulk: can't read without a bulk-out endpoint\n");
+ return SANE_STATUS_INVAL;
+ }
+ if (rc) return SANE_STATUS_INVAL;
+ *size -=ulToWrite;
+ buffer += ulToWrite;
+ write_size += ulToWrite;
+ DBG (5, "size = %d, write_size = %d\n",*size, write_size);
+ }
+#else /* not HAVE_USBCALLS */
+ {
+ DBG (1, "sanei_usb_write_bulk: usbcalls support missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+ }
+#endif /* not HAVE_USBCALLS */
+ }
+ else
+ {
+ DBG (1, "sanei_usb_write_bulk: access method %d not implemented\n",
+ devices[dn].method);
+ return SANE_STATUS_INVAL;
+ }
+ } while (write_size < 0
+#if defined(HAVE_LIBUSB)
+ && (EAGAIN == need_retry || EWOULDBLOCK == need_retry)
+#elif defined(HAVE_LIBUSB_1_0)
+ && LIBUSB_ERROR_BUSY == need_retry
+#endif /* HAVE_LIBUSB_1_0 */
+ && max_tries > tries);
+
+ if (write_size < 0)
+ {
+ *size = 0;
+#ifdef HAVE_LIBUSB
+ if (devices[dn].method == sanei_usb_method_libusb)
+ usb_clear_halt (devices[dn].libusb_handle, devices[dn].bulk_out_ep);
+#elif defined(HAVE_LIBUSB_1_0)
+ if (devices[dn].method == sanei_usb_method_libusb)
+ libusb_clear_halt (devices[dn].lu_handle, devices[dn].bulk_out_ep);
+#endif
+ return SANE_STATUS_IO_ERROR;
+ }
+ DBG (5, "sanei_usb_write_bulk: wanted %lu bytes, wrote %ld bytes\n",
+ (unsigned long) *size, (unsigned long) write_size);
+ *size = write_size;
+ return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+sanei_usb_control_msg (SANE_Int dn, SANE_Int rtype, SANE_Int req,
+ SANE_Int value, SANE_Int index, size_t * size,
+ SANE_Byte * data)
+{
+ SANE_Int len = *size;
+
+ if (dn >= device_number || dn < 0)
+ {
+ DBG (1, "sanei_usb_control_msg: dn >= device number || dn < 0, dn=%d\n",
+ dn);
+ return SANE_STATUS_INVAL;
+ }
+
+ DBG (5, "sanei_usb_control_msg: rtype = 0x%02x, req = %d, value = %d, "
+ "index = %d, len = %d\n", rtype, req, value, index, len);
+ if (!(rtype & 0x80) && debug_level > 10)
+ print_buffer (data, len);
+
+ if (devices[dn].method == sanei_usb_method_scanner_driver)
+ {
+#if defined(__linux__)
+ struct ctrlmsg_ioctl c;
+
+ c.req.requesttype = rtype;
+ c.req.request = req;
+ c.req.value = value;
+ c.req.index = index;
+ c.req.length = len;
+ c.data = data;
+
+ if (ioctl (devices[dn].fd, SCANNER_IOCTL_CTRLMSG, &c) < 0)
+ {
+ DBG (5, "sanei_usb_control_msg: SCANNER_IOCTL_CTRLMSG error - %s\n",
+ strerror (errno));
+ return SANE_STATUS_IO_ERROR;
+ }
+ /* Assuming ioctl() updates c.req.length to hold bytes read. Unverified. */
+ len = c.req.length;
+ *size = len;
+ if ((rtype & 0x80) && debug_level > 10)
+ print_buffer (data, len);
+ return SANE_STATUS_GOOD;
+#elif defined(__BEOS__)
+ struct usb_scanner_ioctl_ctrlmsg c;
+
+ c.req.request_type = rtype;
+ c.req.request = req;
+ c.req.value = value;
+ c.req.index = index;
+ c.req.length = len;
+ c.data = data;
+
+ if (ioctl (devices[dn].fd, B_SCANNER_IOCTL_CTRLMSG, &c) < 0)
+ {
+ DBG (5, "sanei_usb_control_msg: SCANNER_IOCTL_CTRLMSG error - %s\n",
+ strerror (errno));
+ return SANE_STATUS_IO_ERROR;
+ }
+ /* Assuming ioctl() updates c.req.length to hold bytes read. Unverified. */
+ len = c.req.length;
+ *size = len;
+ if ((rtype & 0x80) && debug_level > 10)
+ print_buffer (data, len);
+
+ return SANE_STATUS_GOOD;
+#else /* not __linux__ */
+ DBG (5, "sanei_usb_control_msg: not supported on this OS\n");
+ return SANE_STATUS_UNSUPPORTED;
+#endif /* not __linux__ */
+ }
+ else if (devices[dn].method == sanei_usb_method_libusb)
+#ifdef HAVE_LIBUSB
+ {
+ int result;
+
+ result = usb_control_msg (devices[dn].libusb_handle, rtype, req,
+ value, index, (char *) data, len,
+ libusb_timeout);
+ if (result < 0)
+ {
+ DBG (1, "sanei_usb_control_msg: libusb complained: %s\n",
+ usb_strerror ());
+ return SANE_STATUS_INVAL;
+ }
+ *size = len = result;
+ if ((rtype & 0x80) && debug_level > 10)
+ print_buffer (data, len);
+ return SANE_STATUS_GOOD;
+ }
+#elif defined(HAVE_LIBUSB_1_0)
+ {
+ int result;
+
+ result = libusb_control_transfer (devices[dn].lu_handle, rtype, req,
+ value, index, data, len,
+ libusb_timeout);
+ if (result < 0)
+ {
+ DBG (1, "sanei_usb_control_msg: libusb complained: %s\n",
+ sanei_libusb_strerror (result));
+ return SANE_STATUS_INVAL;
+ }
+ *size = len = result;
+ if ((rtype & 0x80) && debug_level > 10)
+ print_buffer (data, len);
+ return SANE_STATUS_GOOD;
+ }
+#else /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0*/
+ {
+ DBG (1, "sanei_usb_control_msg: libusb support missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+ }
+#endif /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0 */
+ else if (devices[dn].method == sanei_usb_method_usbcalls)
+ {
+#ifdef HAVE_USBCALLS
+ int result;
+
+ result = UsbCtrlMessage (dh, rtype, req,
+ value, index, len, (char *) data,
+ usbcalls_timeout);
+ DBG (5, "rc of usb_control_msg = %d\n",result);
+ if (result < 0)
+ {
+ DBG (1, "sanei_usb_control_msg: usbcalls complained: %d\n",result);
+ return SANE_STATUS_INVAL;
+ }
+ /* Assuming UsbCtrlMessage() returns result to hold bytes read. Unverified. */
+ len = result;
+ *size = len;
+ if ((rtype & 0x80) && debug_level > 10)
+ print_buffer (data, len);
+ return SANE_STATUS_GOOD;
+#else /* not HAVE_USBCALLS */
+ {
+ DBG (1, "sanei_usb_control_msg: usbcalls support missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+ }
+#endif /* not HAVE_USBCALLS */
+ }
+ else
+ {
+ DBG (1, "sanei_usb_control_msg: access method %d not implemented\n",
+ devices[dn].method);
+ return SANE_STATUS_UNSUPPORTED;
+ }
+}
+
+SANE_Status
+sanei_usb_read_int (SANE_Int dn, SANE_Byte * buffer, size_t * size)
+{
+ ssize_t read_size = 0;
+#if defined(HAVE_LIBUSB) || defined(HAVE_LIBUSB_1_0)
+ SANE_Bool stalled = SANE_FALSE;
+#endif
+
+ if (!size)
+ {
+ DBG (1, "sanei_usb_read_int: size == NULL\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ if (dn >= device_number || dn < 0)
+ {
+ DBG (1, "sanei_usb_read_int: dn >= device number || dn < 0\n");
+ return SANE_STATUS_INVAL;
+ }
+
+ DBG (5, "sanei_usb_read_int: trying to read %lu bytes\n",
+ (unsigned long) *size);
+ if (devices[dn].method == sanei_usb_method_scanner_driver)
+ {
+ DBG (1, "sanei_usb_read_int: access method %d not implemented\n",
+ devices[dn].method);
+ return SANE_STATUS_INVAL;
+ }
+ else if (devices[dn].method == sanei_usb_method_libusb)
+#ifdef HAVE_LIBUSB
+ {
+ if (devices[dn].int_in_ep)
+ {
+ read_size = usb_interrupt_read (devices[dn].libusb_handle,
+ devices[dn].int_in_ep,
+ (char *) buffer, (int) *size,
+ libusb_timeout);
+
+ if (read_size < 0)
+ DBG (1, "sanei_usb_read_int: read failed: %s\n",
+ strerror (errno));
+
+ stalled = (read_size == -EPIPE);
+ }
+ else
+ {
+ DBG (1, "sanei_usb_read_int: can't read without an int "
+ "endpoint\n");
+ return SANE_STATUS_INVAL;
+ }
+ }
+#elif defined(HAVE_LIBUSB_1_0)
+ {
+ if (devices[dn].int_in_ep)
+ {
+ int ret;
+ int trans_bytes;
+ ret = libusb_interrupt_transfer (devices[dn].lu_handle,
+ devices[dn].int_in_ep,
+ buffer, (int) *size,
+ &trans_bytes, libusb_timeout);
+
+ if (ret < 0)
+ read_size = -1;
+ else
+ read_size = trans_bytes;
+
+ stalled = (ret == LIBUSB_ERROR_PIPE);
+ }
+ else
+ {
+ DBG (1, "sanei_usb_read_int: can't read without an int "
+ "endpoint\n");
+ return SANE_STATUS_INVAL;
+ }
+ }
+#else /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0 */
+ {
+ DBG (1, "sanei_usb_read_int: libusb support missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+ }
+#endif /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0 */
+ else if (devices[dn].method == sanei_usb_method_usbcalls)
+ {
+#ifdef HAVE_USBCALLS
+ int rc;
+ USHORT usNumBytes=*size;
+ DBG (5, "Entered usbcalls UsbIrqStart with dn = %d\n",dn);
+ DBG (5, "Entered usbcalls UsbIrqStart with dh = %p\n",dh);
+ DBG (5, "Entered usbcalls UsbIrqStart with int_in_ep = 0x%02x\n",devices[dn].int_in_ep);
+ DBG (5, "Entered usbcalls UsbIrqStart with interface_nr = %d\n",devices[dn].interface_nr);
+ DBG (5, "Entered usbcalls UsbIrqStart with bytes to read = %u\n",usNumBytes);
+
+ if (devices[dn].int_in_ep){
+ rc = UsbIrqStart (dh,devices[dn].int_in_ep,devices[dn].interface_nr,
+ usNumBytes, (char *) buffer, pUsbIrqStartHev);
+ DBG (5, "rc of UsbIrqStart = %d\n",rc);
+ }
+ else
+ {
+ DBG (1, "sanei_usb_read_int: can't read without an int "
+ "endpoint\n");
+ return SANE_STATUS_INVAL;
+ }
+ if (rc) return SANE_STATUS_INVAL;
+ read_size += usNumBytes;
+#else
+ DBG (1, "sanei_usb_read_int: usbcalls support missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+#endif /* HAVE_USBCALLS */
+ }
+ else
+ {
+ DBG (1, "sanei_usb_read_int: access method %d not implemented\n",
+ devices[dn].method);
+ return SANE_STATUS_INVAL;
+ }
+
+ if (read_size < 0)
+ {
+#ifdef HAVE_LIBUSB
+ if (devices[dn].method == sanei_usb_method_libusb)
+ if (stalled)
+ usb_clear_halt (devices[dn].libusb_handle, devices[dn].int_in_ep);
+#elif defined(HAVE_LIBUSB_1_0)
+ if (devices[dn].method == sanei_usb_method_libusb)
+ if (stalled)
+ libusb_clear_halt (devices[dn].lu_handle, devices[dn].int_in_ep);
+#endif
+ *size = 0;
+ return SANE_STATUS_IO_ERROR;
+ }
+ if (read_size == 0)
+ {
+ DBG (3, "sanei_usb_read_int: read returned EOF\n");
+ *size = 0;
+ return SANE_STATUS_EOF;
+ }
+ DBG (5, "sanei_usb_read_int: wanted %lu bytes, got %ld bytes\n",
+ (unsigned long) *size, (unsigned long) read_size);
+ *size = read_size;
+ if (debug_level > 10)
+ print_buffer (buffer, read_size);
+
+ return SANE_STATUS_GOOD;
+}
+
+SANE_Status
+sanei_usb_set_configuration (SANE_Int dn, SANE_Int configuration)
+{
+ if (dn >= device_number || dn < 0)
+ {
+ DBG (1,
+ "sanei_usb_set_configuration: dn >= device number || dn < 0, dn=%d\n",
+ dn);
+ return SANE_STATUS_INVAL;
+ }
+
+ DBG (5, "sanei_usb_set_configuration: configuration = %d\n", configuration);
+
+ if (devices[dn].method == sanei_usb_method_scanner_driver)
+ {
+#if defined(__linux__)
+ return SANE_STATUS_GOOD;
+#else /* not __linux__ */
+ DBG (5, "sanei_usb_set_configuration: not supported on this OS\n");
+ return SANE_STATUS_UNSUPPORTED;
+#endif /* not __linux__ */
+ }
+ else if (devices[dn].method == sanei_usb_method_libusb)
+#ifdef HAVE_LIBUSB
+ {
+ int result;
+
+ result =
+ usb_set_configuration (devices[dn].libusb_handle, configuration);
+ if (result < 0)
+ {
+ DBG (1, "sanei_usb_set_configuration: libusb complained: %s\n",
+ usb_strerror ());
+ return SANE_STATUS_INVAL;
+ }
+ return SANE_STATUS_GOOD;
+ }
+#elif defined(HAVE_LIBUSB_1_0)
+ {
+ int result;
+
+ result = libusb_set_configuration (devices[dn].lu_handle, configuration);
+ if (result < 0)
+ {
+ DBG (1, "sanei_usb_set_configuration: libusb complained: %s\n",
+ sanei_libusb_strerror (result));
+ return SANE_STATUS_INVAL;
+ }
+ return SANE_STATUS_GOOD;
+ }
+#else /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0 */
+ {
+ DBG (1, "sanei_usb_set_configuration: libusb support missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+ }
+#endif /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0 */
+ else
+ {
+ DBG (1,
+ "sanei_usb_set_configuration: access method %d not implemented\n",
+ devices[dn].method);
+ return SANE_STATUS_UNSUPPORTED;
+ }
+}
+
+SANE_Status
+sanei_usb_claim_interface (SANE_Int dn, SANE_Int interface_number)
+{
+ if (dn >= device_number || dn < 0)
+ {
+ DBG (1,
+ "sanei_usb_claim_interface: dn >= device number || dn < 0, dn=%d\n",
+ dn);
+ return SANE_STATUS_INVAL;
+ }
+
+ DBG (5, "sanei_usb_claim_interface: interface_number = %d\n", interface_number);
+
+ if (devices[dn].method == sanei_usb_method_scanner_driver)
+ {
+#if defined(__linux__)
+ return SANE_STATUS_GOOD;
+#else /* not __linux__ */
+ DBG (5, "sanei_usb_claim_interface: not supported on this OS\n");
+ return SANE_STATUS_UNSUPPORTED;
+#endif /* not __linux__ */
+ }
+ else if (devices[dn].method == sanei_usb_method_libusb)
+#ifdef HAVE_LIBUSB
+ {
+ int result;
+
+ result = usb_claim_interface (devices[dn].libusb_handle, interface_number);
+ if (result < 0)
+ {
+ DBG (1, "sanei_usb_claim_interface: libusb complained: %s\n",
+ usb_strerror ());
+ return SANE_STATUS_INVAL;
+ }
+ return SANE_STATUS_GOOD;
+ }
+#elif defined(HAVE_LIBUSB_1_0)
+ {
+ int result;
+
+ result = libusb_claim_interface (devices[dn].lu_handle, interface_number);
+ if (result < 0)
+ {
+ DBG (1, "sanei_usb_claim_interface: libusb complained: %s\n",
+ sanei_libusb_strerror (result));
+ return SANE_STATUS_INVAL;
+ }
+ return SANE_STATUS_GOOD;
+ }
+#else /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0 */
+ {
+ DBG (1, "sanei_usb_claim_interface: libusb support missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+ }
+#endif /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0 */
+ else
+ {
+ DBG (1, "sanei_usb_claim_interface: access method %d not implemented\n",
+ devices[dn].method);
+ return SANE_STATUS_UNSUPPORTED;
+ }
+}
+
+SANE_Status
+sanei_usb_release_interface (SANE_Int dn, SANE_Int interface_number)
+{
+ if (dn >= device_number || dn < 0)
+ {
+ DBG (1,
+ "sanei_usb_release_interface: dn >= device number || dn < 0, dn=%d\n",
+ dn);
+ return SANE_STATUS_INVAL;
+ }
+
+ DBG (5, "sanei_usb_release_interface: interface_number = %d\n", interface_number);
+
+ if (devices[dn].method == sanei_usb_method_scanner_driver)
+ {
+#if defined(__linux__)
+ return SANE_STATUS_GOOD;
+#else /* not __linux__ */
+ DBG (5, "sanei_usb_release_interface: not supported on this OS\n");
+ return SANE_STATUS_UNSUPPORTED;
+#endif /* not __linux__ */
+ }
+ else if (devices[dn].method == sanei_usb_method_libusb)
+#ifdef HAVE_LIBUSB
+ {
+ int result;
+
+ result = usb_release_interface (devices[dn].libusb_handle, interface_number);
+ if (result < 0)
+ {
+ DBG (1, "sanei_usb_release_interface: libusb complained: %s\n",
+ usb_strerror ());
+ return SANE_STATUS_INVAL;
+ }
+ return SANE_STATUS_GOOD;
+ }
+#elif defined(HAVE_LIBUSB_1_0)
+ {
+ int result;
+
+ result = libusb_release_interface (devices[dn].lu_handle, interface_number);
+ if (result < 0)
+ {
+ DBG (1, "sanei_usb_release_interface: libusb complained: %s\n",
+ sanei_libusb_strerror (result));
+ return SANE_STATUS_INVAL;
+ }
+ return SANE_STATUS_GOOD;
+ }
+#else /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0 */
+ {
+ DBG (1, "sanei_usb_release_interface: libusb support missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+ }
+#endif /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0 */
+ else
+ {
+ DBG (1,
+ "sanei_usb_release_interface: access method %d not implemented\n",
+ devices[dn].method);
+ return SANE_STATUS_UNSUPPORTED;
+ }
+}
+
+SANE_Status
+sanei_usb_set_altinterface (SANE_Int dn, SANE_Int alternate)
+{
+ if (dn >= device_number || dn < 0)
+ {
+ DBG (1,
+ "sanei_usb_set_altinterface: dn >= device number || dn < 0, dn=%d\n",
+ dn);
+ return SANE_STATUS_INVAL;
+ }
+
+ DBG (5, "sanei_usb_set_altinterface: alternate = %d\n", alternate);
+
+ if (devices[dn].method == sanei_usb_method_scanner_driver)
+ {
+#if defined(__linux__)
+ return SANE_STATUS_GOOD;
+#else /* not __linux__ */
+ DBG (5, "sanei_usb_set_altinterface: not supported on this OS\n");
+ return SANE_STATUS_UNSUPPORTED;
+#endif /* not __linux__ */
+ }
+ else if (devices[dn].method == sanei_usb_method_libusb)
+#ifdef HAVE_LIBUSB
+ {
+ int result;
+
+ result = usb_set_altinterface (devices[dn].libusb_handle, alternate);
+ if (result < 0)
+ {
+ DBG (1, "sanei_usb_set_altinterface: libusb complained: %s\n",
+ usb_strerror ());
+ return SANE_STATUS_INVAL;
+ }
+ return SANE_STATUS_GOOD;
+ }
+#elif defined(HAVE_LIBUSB_1_0)
+ {
+ int result;
+
+ result = libusb_set_interface_alt_setting (devices[dn].lu_handle,
+ devices[dn].interface_nr, alternate);
+ if (result < 0)
+ {
+ DBG (1, "sanei_usb_set_altinterface: libusb complained: %s\n",
+ sanei_libusb_strerror (result));
+ return SANE_STATUS_INVAL;
+ }
+ return SANE_STATUS_GOOD;
+ }
+#else /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0 */
+ {
+ DBG (1, "sanei_set_altinterface: libusb support missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+ }
+#endif /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0 */
+ else
+ {
+ DBG (1,
+ "sanei_usb_set_altinterface: access method %d not implemented\n",
+ devices[dn].method);
+ return SANE_STATUS_UNSUPPORTED;
+ }
+}
+
+extern SANE_Status
+sanei_usb_get_descriptor( SANE_Int dn, struct sanei_usb_dev_descriptor *desc )
+{
+ if (dn >= device_number || dn < 0)
+ {
+ DBG (1,
+ "sanei_usb_get_descriptor: dn >= device number || dn < 0, dn=%d\n",
+ dn);
+ return SANE_STATUS_INVAL;
+ }
+
+ DBG (5, "sanei_usb_get_descriptor\n");
+#ifdef HAVE_LIBUSB
+ {
+ struct usb_device_descriptor *usb_descr;
+
+ usb_descr = &(devices[dn].libusb_device->descriptor);
+ desc->desc_type = usb_descr->bDescriptorType;
+ desc->bcd_usb = usb_descr->bcdUSB;
+ desc->bcd_dev = usb_descr->bcdDevice;
+ desc->dev_class = usb_descr->bDeviceClass;
+
+ desc->dev_sub_class = usb_descr->bDeviceSubClass;
+ desc->dev_protocol = usb_descr->bDeviceProtocol;
+ desc->max_packet_size = usb_descr->bMaxPacketSize0;
+ return SANE_STATUS_GOOD;
+ }
+#elif defined(HAVE_LIBUSB_1_0)
+ {
+ struct libusb_device_descriptor lu_desc;
+ int ret;
+
+ ret = libusb_get_device_descriptor (devices[dn].lu_device, &lu_desc);
+ if (ret < 0)
+ {
+ DBG (1,
+ "sanei_usb_get_descriptor: libusb error: %s\n",
+ sanei_libusb_strerror (ret));
+
+ return SANE_STATUS_INVAL;
+ }
+
+ desc->desc_type = lu_desc.bDescriptorType;
+ desc->bcd_usb = lu_desc.bcdUSB;
+ desc->bcd_dev = lu_desc.bcdDevice;
+ desc->dev_class = lu_desc.bDeviceClass;
+
+ desc->dev_sub_class = lu_desc.bDeviceSubClass;
+ desc->dev_protocol = lu_desc.bDeviceProtocol;
+ desc->max_packet_size = lu_desc.bMaxPacketSize0;
+ return SANE_STATUS_GOOD;
+ }
+#else /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0 */
+ {
+ DBG (1, "sanei_usb_get_descriptor: libusb support missing\n");
+ return SANE_STATUS_UNSUPPORTED;
+ }
+#endif /* not HAVE_LIBUSB && not HAVE_LIBUSB_1_0 */
+}
diff --git a/scm-build.am b/scm-build.am
new file mode 100644
index 0000000..afc1fcc
--- /dev/null
+++ b/scm-build.am
@@ -0,0 +1,110 @@
+## scm-build.am -- -*- automake -*- snippet to ease binary packaging
+## Copyright (C) 2019 SEIKO EPSON Corporation
+##
+## License: cc-by-sa-3.0
+## Authors: Olaf Meeuwissen
+##
+## This file is courtesy of the 'SCM Build' package.
+## The 'SCM Build' package itself is free software, released 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.
+##
+## This file, however, has been made available under the terms of the
+## Creative Commons Attribution-Share Alike 3.0 Unported License.
+## See <http://creativecommons.org/licenses/by-sa/3.0/> for details.
+##
+## Include this file in your top-level Makefile.am and make sure that
+## you define the following variables in that Makefile.am:
+##
+## EXTRA_DIST
+## MAINTAINERCLEANFILES
+## MAINTAINERCLEANLOCAL
+
+
+## Source packaging support
+
+
+## Debian packaging support
+
+## Adding files such as debian/rules.in and debian/control.in to the
+## list of AC_CONFIG_FILES does not work as one would expect. Files
+## listed become candidates for removal by `make distclean`.
+##
+## With the regular Debian build machinery doing exactly that at the
+## very start of just about any build, said build machinery will, as
+## a result, have understandable trouble finding usable debian/rules
+## and debian/control files.
+
+
+EXTRA_DIST += $(scm_deb_builddir)/source/format
+MAINTAINERCLEANFILES += $(scm_deb_builddir)/source/format
+$(scm_deb_builddir)/source/format:
+ test -d $(scm_deb_builddir)/source \
+ || $(mkinstalldirs) $(scm_deb_builddir)/source
+ echo 1.0 > $@
+
+EXTRA_DIST += $(scm_deb_builddir)/changelog
+MAINTAINERCLEANFILES += $(scm_deb_builddir)/changelog
+$(scm_deb_builddir)/changelog:
+ test -d $(scm_deb_builddir) || $(mkinstalldirs) $(scm_deb_builddir)
+ echo "$(PACKAGE_TARNAME) ($(PACKAGE_VERSION)-$(scm_src_release)) $(scm_deb_dists); urgency=$(scm_deb_urgency)" > $@
+ echo "" >> $@
+ echo " * latest \"upstream\"" >> $@
+ echo " See the NEWS and/or ChangeLog files for details." >> $@
+ echo "" >> $@
+ echo " -- $(scm_deb_maint) <$(scm_deb_maint_email)> `LC_TIME=C date --rfc-2822`" >> $@
+
+.PHONY: $(scm_deb_srcdir)/changelog
+
+if !scm_deb_have_control_in
+EXTRA_DIST += $(scm_deb_srcdir)/control
+else
+EXTRA_DIST += $(scm_deb_srcdir)/control.in $(scm_deb_builddir)/control
+MAINTAINERCLEANFILES += $(scm_deb_builddir)/control
+$(scm_deb_builddir)/control: $(scm_deb_srcdir)/control.in $(top_srcdir)/configure
+ cd $(top_builddir) && $(SHELL) ./config.status --file $@:$<
+ sed -i \
+ -e '/^Description:/,/^$$/{ s/\\$$//; s/^/ / }' \
+ -e '/^ Description:/s/^ //' $@
+endif
+
+if !scm_deb_have_copyright_in
+EXTRA_DIST += $(scm_deb_srcdir)/copyright
+else
+EXTRA_DIST += $(scm_deb_srcdir)/copyright.in $(scm_deb_builddir)/copyright
+MAINTAINERCLEANFILES += $(scm_deb_builddir)/copyright
+$(scm_deb_builddir)/copyright: $(scm_deb_srcdir)/copyright.in $(top_srcdir)/configure
+ cd $(top_builddir) && $(SHELL) ./config.status --file $@:$<
+endif
+
+if !scm_deb_have_rules_in
+EXTRA_DIST += $(scm_deb_srcdir)/rules
+else
+EXTRA_DIST += $(scm_deb_srcdir)/rules.in $(scm_deb_builddir)/rules
+MAINTAINERCLEANFILES += $(scm_deb_builddir)/rules
+$(scm_deb_builddir)/rules: $(scm_deb_srcdir)/rules.in $(top_srcdir)/configure
+ cd $(top_builddir) && $(SHELL) ./config.status --file $@:$<
+ chmod ugo+x $@
+endif
+
+## RPM packaging support
+
+if !scm_rpm_have_spec_in
+EXTRA_DIST += $(top_srcdir)/$(scm_rpm_spec)
+else
+EXTRA_DIST += $(top_srcdir)/$(scm_rpm_spec_in) $(top_builddir)/$(scm_rpm_spec)
+MAINTAINERCLEANFILES += $(top_builddir)/$(scm_rpm_spec)
+MAINTAINERCLEANLOCAL += scm-rpm-spec-file
+if scm_rpm_spec_is_srcdir_file
+scm-rpm-spec-file:
+ echo "To be generated from $(top_srcdir)/$(scm_rpm_spec_in)" \
+ > $(top_builddir)/$(scm_rpm_spec)
+ touch -d @0 $(top_builddir)/$(scm_rpm_spec)
+endif
+$(top_builddir)/$(scm_rpm_spec): $(top_srcdir)/$(scm_rpm_spec_in) $(top_srcdir)/configure
+ cd $(top_builddir) && $(SHELL) ./config.status --file $@:$<
+ sed -i '/^%description/,/^$$/{ s/\\$$//; s/^ //; s/^\.$$// }' $@
+endif
+
+maintainer-clean-local: $(MAINTAINERCLEANLOCAL)
diff --git a/utils/Makefile.am b/utils/Makefile.am
new file mode 100644
index 0000000..9d592db
--- /dev/null
+++ b/utils/Makefile.am
@@ -0,0 +1,38 @@
+## Makefile.am -- an automake template for a Makefile.in file
+## Copyright (C) 2005--2006, 2008, 2009 Olaf Meeuwissen
+##
+## This file is part of the "Image Scan!" build infra-structure.
+##
+## The "Image Scan!" build infra-structure is free software.
+## You can redistribute it and/or modify it under the terms of the GNU
+## General Public License as published by the Free Software Foundation;
+## either version 2 of the License or at your option any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of FITNESS
+## FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+## See the GNU General Public License for more details.
+##
+## You should have received a verbatim copy of the GNU General Public
+## License along with this program; if not, write to:
+##
+## Free Software Foundation, Inc.
+## 59 Temple Place, Suite 330
+## Boston, MA 02111-1307 USA
+
+
+sbin_SCRIPTS = \
+ iscan-registry
+
+iscan-registry: iscan-registry.in Makefile
+ $(SHELL) ../config.status --file $@:$<
+ sed -i \
+ -e 's,[@]PKGLOCALSTATEDIR[@],$(localstatedir)/lib/$(PACKAGE_TARNAME),g' $@
+ chmod +x $@
+
+CLEANFILES = \
+ config.log \
+ iscan-registry
+
+EXTRA_DIST = \
+ iscan-registry.in
diff --git a/utils/iscan-registry.in b/utils/iscan-registry.in
new file mode 100644
index 0000000..1f48630
--- /dev/null
+++ b/utils/iscan-registry.in
@@ -0,0 +1,167 @@
+#! /bin/sh
+# iscan-registry.in -- adds/removes information from "sub"packages
+# Copyright (C) 2008 SEIKO EPSON CORPORATION
+#
+# License: GPLv2+
+# Authors: AVASYS CORPORATION
+#
+# This file is part of the 'Image Scan! for Linux' package.
+# This package is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License or, at
+# your option, any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You ought to have received a copy of the GNU General Public License
+# along with this package. If not, see <http://www.gnu.org/licenses/>.
+
+
+STATEDIR=@PKGLOCALSTATEDIR@
+
+# Output a version blurb.
+
+version () {
+ cat <<EOF
+`basename $0` (@PACKAGE_NAME@) @PACKAGE_VERSION@
+Copyright (C) 2008 @scm_src_vendor@
+This is free software. You may redistribute copies of it under the
+terms of the GNU General Public License, version 2 or later.
+See <http://www.gnu.org/licenses/gpl.html> for details.
+
+Written by @scm_src_author@.
+EOF
+ if test xyes = x$1; then
+ echo
+ usage
+ fi
+ exit
+}
+
+# Shows script usage documentation and exits the program with the
+# optional status passed as its first argument.
+
+usage () {
+ cat <<EOF
+'`basename $0`' updates the run-time data for @PACKAGE_NAME@
+
+Usage: $0 --help | --version
+ $0 --add <spec>
+ $0 --remove <spec>
+
+@PACKAGE_NAME@ plugins may need to (un)register themselves.
+This utility gives plugins a common interface to do just that. You
+should not need to use this utility manually.
+
+The following options are supported:
+
+ -h, --help displays this message and exit
+ -v, --version displays program version and exit
+
+ -a, --add adds a plugin to the run-time data
+ -r, --remove removes a plugin from the run-time data
+
+The following <spec>s are supported:
+
+ interpreter usb <vendor-id> <product-id> <plugin> [<firmware>]
+
+The <vendor-id> and <product-id> are the USB IDs in hexadecimal
+notation, prefixed with '0x'. <plugin> and optional <firmware>
+are pathnames to the corresponding files.
+Note that the <plugin> pathname should not include an extension.
+EOF
+ exit $1
+}
+
+show_help=no
+show_vers=no
+
+run_mode=
+
+options=`getopt \
+ --options hvar \
+ --longopt help,version \
+ --longopt add,remove \
+ -- "$@"`
+
+if test 0 != $?; then
+ echo "`basename $0`: error parsing command line options" >&2
+ usage 1
+fi
+
+# Sets and makes sure only a single run mode is specified.
+
+set_run_mode () {
+ if test x$run_mode != x; then
+ echo "`basename $0`: use only one of --add and --remove" >&2
+ exit 1
+ fi
+ run_mode=$1
+}
+
+eval set -- "$options"
+
+while test x-- != "x$1"; do
+ case "$1" in
+ -h|--help) show_help=yes; shift;;
+ -v|--version) show_vers=yes; shift;;
+ -a|--add) set_run_mode add; shift;;
+ -r|--remove) set_run_mode remove; shift;;
+ *)
+ echo "`basename $0`: internal inconsistency"
+ exit 1
+ ;;
+ esac
+done
+shift # past the '--' marker
+
+test xyes = x$show_vers && version $show_help
+test xyes = x$show_help && usage
+
+# If --version or --help was specified, we will have exited by now.
+
+if test x$run_mode = x; then
+ echo "`basename $0`: at least one option is required" >&2
+ exit 1
+fi
+if test 5 -gt $# || test 6 -lt $#; then
+ echo "`basename $0`: not enough parameters" >&2
+ exit 1
+fi
+
+# Validate at least the first argument to avoid making arbitrary
+# files in STATEDIR.
+
+case "$1" in
+ interpreter)
+ config="$STATEDIR/$1"
+ chspec="$1 $2 $3 $4"
+ plugin="$5"
+ fwfile="$6"
+ ;;
+ *)
+ echo "`basename $0`: $1 not supported" >&2
+ exit 1
+ ;;
+esac
+
+
+case "$run_mode" in
+ add)
+ test -d "$STATEDIR" || mkdir -p "$STATEDIR"
+ echo "$chspec $plugin $fwfile" >> "$config"
+ ;;
+ remove)
+ test -f "$config" || exit 0
+ pattern=`echo "$chspec $plugin $fwfile" | sed 's|/|\\\\/|g'`
+ first=`sed -n "/$pattern/{ =; q}" "$config"`
+ test -n "${first}" && sed -i "${first}d" "$config"
+ ;;
+ *)
+ echo "`basename $0`: internal inconsistency" >&2
+ exit 1
+ ;;
+esac