Package Gnumed :: Package pycommon :: Module gmScanBackend
[frames] | no frames]

Source Code for Module Gnumed.pycommon.gmScanBackend

  1  #================================================== 
  2  # GNUmed SANE/TWAIN scanner classes 
  3  #================================================== 
  4  # $Source: /cvsroot/gnumed/gnumed/gnumed/client/pycommon/gmScanBackend.py,v $ 
  5  # $Id: gmScanBackend.py,v 1.56 2009/12/21 15:02:51 ncq Exp $ 
  6  __version__ = "$Revision: 1.56 $" 
  7  __license__ = "GPL" 
  8  __author__ = """Sebastian Hilbert <Sebastian.Hilbert@gmx.net>, Karsten Hilbert <Karsten.Hilbert@gmx.net>""" 
  9   
 10  #================================================== 
 11  # stdlib 
 12  import sys, os.path, os, string, time, shutil, codecs, glob, locale, errno, stat, logging 
 13   
 14   
 15  # 3rd party 
 16  #import Image 
 17   
 18   
 19  # GNUmed 
 20  if __name__ == '__main__': 
 21          sys.path.insert(0, '../../') 
 22  from Gnumed.pycommon import gmShellAPI, gmTools, gmI18N 
 23   
 24   
 25  _log = logging.getLogger('gm.scanning') 
 26  _log.info(__version__) 
 27   
 28  _twain_module = None 
 29  _sane_module = None 
 30   
 31  use_XSane = True 
 32  #======================================================= 
 33  # TWAIN handling 
 34  #======================================================= 
35 -def _twain_import_module():
36 global _twain_module 37 if _twain_module is None: 38 try: 39 import twain 40 _twain_module = twain 41 except ImportError: 42 _log.exception('cannot import TWAIN module (WinTWAIN.py)') 43 raise 44 _log.info("TWAIN version: %s" % _twain_module.Version())
45 #=======================================================
46 -class cTwainScanner:
47 48 # FIXME: we need to handle this exception in the right place: <class 'twain.excTWCC_SUCCESS'> 49
50 - def __init__(self, calling_window=None):
51 _twain_import_module() 52 53 self.__calling_window = calling_window 54 self.__src_manager = None 55 self.__scanner = None 56 self.__done_transferring_image = False 57 58 self.__register_event_handlers()
59 #--------------------------------------------------- 60 # external API 61 #---------------------------------------------------
62 - def acquire_pages_into_files(self, delay=None, filename=None, tmpdir=None):
63 if filename is None: 64 filename = gmTools.get_unique_filename(prefix='gmScannedObj-', suffix='.bmp', tmp_dir=tmpdir) 65 else: 66 tmp, ext = os.path.splitext(filename) 67 if ext != '.bmp': 68 filename = filename + '.bmp' 69 70 self.__filename = os.path.abspath(os.path.expanduser(filename)) 71 72 if not self.__init_scanner(): 73 raise OSError(-1, 'cannot init TWAIN scanner device') 74 75 self.__done_transferring_image = False 76 self.__scanner.RequestAcquire(True) 77 78 return [self.__filename]
79 #---------------------------------------------------
80 - def image_transfer_done(self):
81 return self.__done_transferring_image
82 #---------------------------------------------------
83 - def close(self):
84 # close() is called after acquire_pages*() so if we destroy the source 85 # before TWAIN is done we hang it, an RequestAcquire() only *requests* 86 # a scan, we would have to wait for process_xfer to finisch before 87 # destroying the source, and even then it might destroy state in the 88 # non-Python TWAIN subsystem 89 #********************************** 90 # if we do this TWAIN does not work 91 #********************************** 92 # if self.__scanner is not None: 93 # self.__scanner.destroy() 94 95 # if self.__src_manager is not None: 96 # self.__src_manager.destroy() 97 98 # del self.__scanner 99 # del self.__src_manager 100 return
101 #--------------------------------------------------- 102 # internal helpers 103 #---------------------------------------------------
104 - def __init_scanner(self):
105 if self.__scanner is not None: 106 return True 107 108 self.__init_src_manager() 109 if self.__src_manager is None: 110 return False 111 112 # TWAIN will notify us when the image is scanned 113 self.__src_manager.SetCallback(self._twain_event_callback) 114 115 # no arg == show "select source" dialog 116 self.__scanner = self.__src_manager.OpenSource() 117 if self.__scanner is None: 118 _log.error("user canceled scan source selection dialog") 119 return False 120 121 _log.info("TWAIN data source: %s" % self.__scanner.GetSourceName()) 122 _log.debug("TWAIN data source config: %s" % str(self.__scanner.GetIdentity())) 123 124 return True
125 #---------------------------------------------------
126 - def __init_src_manager(self):
127 # open scanner manager 128 if self.__src_manager is not None: 129 return 130 131 # clean up scanner driver since we will initialize the source manager 132 # if self.__scanner is not None: 133 # self.__scanner.destroy() # this probably should not be done here 134 # del self.__scanner # try to sneak this back in later 135 # self.__scanner = None # this really should work 136 137 # TWAIN talks to us via MS-Windows message queues 138 # so we need to pass it a handle to ourselves, 139 # the following fails with "attempt to create Pseudo Window failed", 140 # I assume because the TWAIN vendors want to sabotage rebranding their GUI 141 # self.__src_manager = _twain_module.SourceManager(self.__calling_window.GetHandle(), ProductName = 'GNUmed - The EMR that never sleeps.') 142 self.__src_manager = _twain_module.SourceManager(self.__calling_window.GetHandle()) 143 144 _log.info("TWAIN source manager config: %s" % str(self.__src_manager.GetIdentity()))
145 #--------------------------------------------------- 146 # TWAIN callback handling 147 #---------------------------------------------------
149 self.__twain_event_handlers = { 150 _twain_module.MSG_XFERREADY: self._twain_handle_transfer_in_memory, 151 _twain_module.MSG_CLOSEDSREQ: self._twain_close_datasource, 152 _twain_module.MSG_CLOSEDSOK: self._twain_save_state, 153 _twain_module.MSG_DEVICEEVENT: self._twain_handle_src_event 154 }
155 #---------------------------------------------------
156 - def _twain_event_callback(self, twain_event):
157 _log.debug('notification of TWAIN event <%s>' % str(twain_event)) 158 self.__twain_event_handlers[twain_event]() 159 self.__scanner = None 160 return
161 #---------------------------------------------------
162 - def _twain_close_datasource(self):
163 _log.info("being asked to close data source")
164 #---------------------------------------------------
165 - def _twain_save_state(self):
166 _log.info("being asked to save application state")
167 #---------------------------------------------------
168 - def _twain_handle_src_event(self):
169 _log.info("being asked to handle device specific event")
170 #---------------------------------------------------
172 173 # FIXME: handle several images 174 175 _log.debug('receiving image from TWAIN source') 176 _log.debug('image info: %s' % self.__scanner.GetImageInfo()) 177 _log.debug('image layout: %s' % str(self.__scanner.GetImageLayout())) 178 179 # get image from source 180 (external_data_handle, more_images_pending) = self.__scanner.XferImageNatively() 181 try: 182 # convert DIB to standard bitmap file (always .bmp) 183 _twain_module.DIBToBMFile(external_data_handle, self.__filename) 184 finally: 185 _twain_module.GlobalHandleFree(external_data_handle) 186 _log.debug('%s pending images' % more_images_pending) 187 188 # hide the scanner user interface again 189 # self.__scanner.HideUI() # needed ? 190 # self.__scanner = None # not sure why this should be needed, simple_wx does it, though 191 192 self.__done_transferring_image = True
193 #---------------------------------------------------
195 196 # the docs say this is not required to be implemented 197 # therefor we can't use it by default :-( 198 # UNTESTED !!!! 199 200 _log.debug('receiving image from TWAIN source') 201 _log.debug('image info: %s' % self.__scanner.GetImageInfo()) 202 _log.debug('image layout: %s' % self.__scanner.GetImageLayout()) 203 204 self.__scanner.SetXferFileName(self.__filename) # FIXME: allow format 205 206 more_images_pending = self.__scanner.XferImageByFile() 207 _log.debug('%s pending images' % more_images_pending) 208 209 # hide the scanner user interface again 210 self.__scanner.HideUI() 211 # self.__scanner = None 212 213 return
214 #======================================================= 215 # SANE handling 216 #=======================================================
217 -def _sane_import_module():
218 global _sane_module 219 if _sane_module is None: 220 try: 221 import sane 222 except ImportError: 223 _log.exception('cannot import SANE module') 224 raise 225 _sane_module = sane 226 try: 227 init_result = _sane_module.init() 228 except: 229 _log.exception('cannot init SANE module') 230 raise 231 _log.info("SANE version: %s" % str(init_result)) 232 _log.debug('SANE device list: %s' % str(_sane_module.get_devices()))
233 #=======================================================
234 -class cSaneScanner:
235 236 # for testing uncomment "test" backend in /etc/sane/dll.conf 237 238 _src_manager = None 239
240 - def __init__(self, device=None):
241 _sane_import_module() 242 243 # FIXME: need to test against devs[x][0] 244 # devs = _sane_module.get_devices() 245 # if device not in devs: 246 # _log.error("device [%s] not found in list of devices detected by SANE" % device) 247 # _log.error(str(devs)) 248 # raise gmExceptions.ConstructorError, msg 249 250 self.__device = device 251 _log.info('using SANE device [%s]' % self.__device) 252 253 self.__init_scanner()
254 #---------------------------------------------------
255 - def __init_scanner(self):
256 self.__scanner = _sane_module.open(self.__device) 257 258 _log.debug('opened SANE device: %s' % str(self.__scanner)) 259 _log.debug('SANE device config: %s' % str(self.__scanner.get_parameters())) 260 _log.debug('SANE device opts : %s' % str(self.__scanner.optlist)) 261 _log.debug('SANE device opts : %s' % str(self.__scanner.get_options())) 262 263 return True
264 #---------------------------------------------------
265 - def close(self):
266 self.__scanner.close()
267 #---------------------------------------------------
268 - def acquire_pages_into_files(self, delay=None, filename=None, tmpdir=None):
269 if filename is None: 270 filename = gmTools.get_unique_filename(prefix='gmScannedObj-', suffix='.bmp', tmp_dir=tmpdir) 271 else: 272 tmp, ext = os.path.splitext(filename) 273 if ext != '.bmp': 274 filename = filename + '.bmp' 275 276 filename = os.path.abspath(os.path.expanduser(filename)) 277 278 if delay is not None: 279 time.sleep(delay) 280 _log.debug('some sane backends report device_busy if we advance too fast. delay set to %s sec' % delay) 281 282 _log.debug('Trying to get image from scanner into [%s] !' % filename) 283 self.__scanner.start() 284 img = self.__scanner.snap() 285 img.save(filename) 286 287 return [filename]
288 #---------------------------------------------------
289 - def image_transfer_done(self):
290 return True
291 #--------------------------------------------------- 292 # def dummy(self): 293 # pass 294 # # supposedly there is a method *.close() but it does not 295 # # seem to work, therefore I put in the following line (else 296 # # it reports a busy sane-device on the second and consecutive runs) 297 # try: 298 # # by default use the first device 299 # # FIXME: room for improvement - option 300 # self.__scanner = _sane_module.open(_sane_module.get_devices()[0][0]) 301 # except: 302 # _log.exception('cannot open SANE scanner') 303 # return False 304 # 305 # # Set scan parameters 306 # # FIXME: get those from config file 307 # #self.__scannercontrast=170 ; self.__scannerbrightness=150 ; self.__scannerwhite_level=190 308 # #self.__scannerdepth=6 309 # #self.__scannerbr_x = 412.0 310 # #self.__scannerbr_y = 583.0 311 312 #================================================== 313 # XSane handling 314 #==================================================
315 -class cXSaneScanner:
316 317 _filetype = '.png' # FIXME: configurable, TIFF ? 318 _xsanerc = os.path.expanduser(os.path.join('~', '.sane', 'xsane', 'xsane.rc')) 319 #_xsanerc_backup = os.path.expanduser(os.path.join('~', '.sane', 'xsane', 'xsane.rc.gnumed.bak')) 320 _xsanerc_gnumed = os.path.expanduser(os.path.join('~', '.gnumed', 'gnumed-xsanerc.conf')) 321 _xsanerc_backup = os.path.expanduser(os.path.join('~', '.gnumed', 'gnumed-xsanerc.conf.bak')) 322 323 #----------------------------------------------
324 - def __init__(self):
325 # while not strictly necessary it is good to fail early 326 # this will tell us fairly safely whether XSane is properly installed 327 try: 328 open(cXSaneScanner._xsanerc, 'r').close() 329 except IOError: 330 msg = ( 331 'XSane not properly installed for this user:\n\n' 332 ' [%s] not found\n\n' 333 'Start XSane once before using it with GNUmed.' 334 ) % cXSaneScanner._xsanerc 335 raise ImportError(msg) 336 # if not os.access(cXSaneScanner._xsanerc, os.R_OK): 337 # raise ImportError('XSane not properly installed for this user, no write access for [%s]' % cXSaneScanner._xsanerc) 338 339 self.device_settings_file = None 340 self.default_device = None
341 #----------------------------------------------
342 - def close(self):
343 pass
344 #----------------------------------------------
345 - def acquire_pages_into_files(self, delay=None, filename=None, tmpdir=None):
346 """Call XSane. 347 348 <filename> name part must have format name-001.ext> 349 """ 350 if filename is None: 351 filename = gmTools.get_unique_filename ( 352 prefix = 'gmScannedObj-', 353 suffix = cXSaneScanner._filetype, 354 tmp_dir = tmpdir 355 ) 356 name, ext = os.path.splitext(filename) 357 filename = '%s-001%s' % (name, cXSaneScanner._filetype) 358 359 filename = os.path.abspath(os.path.expanduser(filename)) 360 path, name = os.path.split(filename) 361 362 self.__prepare_xsanerc(tmpdir=path) 363 364 cmd = 'xsane --no-mode-selection --save --force-filename "%s" --xsane-rc "%s" %s %s' % ( 365 filename, 366 cXSaneScanner._xsanerc_gnumed, 367 gmTools.coalesce(self.device_settings_file, '', '--device-settings %s'), 368 gmTools.coalesce(self.default_device, '') 369 ) 370 normal_exit = gmShellAPI.run_command_in_shell(command = cmd, blocking = True) 371 372 if normal_exit: 373 flist = glob.glob(filename.replace('001', '*')) 374 flist.sort() 375 return flist 376 377 raise ImportError('error starting XSane as [%s]' % cmd)
378 #---------------------------------------------------
379 - def image_transfer_done(self):
380 return True
381 #---------------------------------------------- 382 # internal API 383 #----------------------------------------------
384 - def __prepare_xsanerc(self, tmpdir=None):
385 386 try: 387 open(cXSaneScanner._xsanerc_gnumed, 'r+b').close() 388 except IOError: 389 _log.info('creating [%s] from [%s]', cXSaneScanner._xsanerc_gnumed, cXSaneScanner._xsanerc) 390 shutil.copyfile(cXSaneScanner._xsanerc, cXSaneScanner._xsanerc_gnumed) 391 392 shutil.move(cXSaneScanner._xsanerc_gnumed, cXSaneScanner._xsanerc_backup) 393 394 # our closest bet, might contain umlauts 395 enc = gmI18N.get_encoding() 396 fread = codecs.open(cXSaneScanner._xsanerc_backup, mode = "rU", encoding = enc) 397 fwrite = codecs.open(cXSaneScanner._xsanerc_gnumed, mode = "w", encoding = enc) 398 399 val_dict = { 400 'filetype': cXSaneScanner._filetype, 401 'tmp-path': tmpdir, 402 'working-directory': tmpdir, 403 'skip-existing-numbers': '1', 404 'filename-counter-step': '1', 405 'filename-counter-len': '3' 406 } 407 408 for idx, line in enumerate(fread): 409 line = line.replace(fread.newlines, '') 410 411 if idx % 2 == 0: # even lines are keys 412 key = line.strip('"') 413 fwrite.write('"%s"%s' % (key, fread.newlines)) 414 else: # odd lines are corresponding values 415 try: 416 value = val_dict[key] 417 except KeyError: 418 value = line 419 fwrite.write('%s%s' % (value, fread.newlines)) 420 421 fwrite.flush() 422 fwrite.close() 423 fread.close() 424 425 #os.chmod(cXSaneScanner._xsanerc, stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) 426 427 return True
428 #---------------------------------------------- 429 # def __restore_xsanerc(self): 430 # shutil.copy2(cXSaneScanner._xsanerc_backup, cXSaneScanner._xsanerc) 431 # #os.chmod(cXSaneScanner._xsanerc, stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) 432 #==================================================
433 -def get_devices():
434 try: 435 _twain_import_module() 436 # TWAIN does not support get_devices(): 437 # devices can only be selected from within TWAIN itself 438 return None 439 except ImportError: 440 pass 441 442 if use_XSane: 443 # neither does XSane 444 return None 445 446 _sane_import_module() 447 return _sane_module.get_devices()
448 #-----------------------------------------------------
449 -def acquire_pages_into_files(device=None, delay=None, filename=None, tmpdir=None, calling_window=None, xsane_device_settings=None):
450 """Connect to a scanner and return the scanned pages as a file list. 451 452 returns: 453 - list of filenames: names of scanned pages, may be [] 454 - None: unable to connect to scanner 455 """ 456 try: 457 scanner = cTwainScanner(calling_window=calling_window) 458 _log.debug('using TWAIN') 459 except ImportError: 460 if use_XSane: 461 _log.debug('using XSane') 462 scanner = cXSaneScanner() 463 scanner.device_settings_file = xsane_device_settings 464 scanner.default_device = device 465 else: 466 _log.debug('using SANE directly') 467 scanner = cSaneScanner(device=device) 468 469 _log.debug('requested filename: [%s]' % filename) 470 fnames = scanner.acquire_pages_into_files(filename=filename, delay=delay, tmpdir=tmpdir) 471 scanner.close() 472 _log.debug('acquired pages into files: %s' % str(fnames)) 473 474 return fnames
475 #================================================== 476 # main 477 #================================================== 478 if __name__ == '__main__': 479 480 if len(sys.argv) > 1 and sys.argv[1] == u'test': 481 482 logging.basicConfig(level=logging.DEBUG) 483 484 print "devices:" 485 print get_devices() 486 487 sys.exit() 488 489 setups = [ 490 {'dev': 'test:0', 'file': 'x1-test0-1-0001'}, 491 {'dev': 'test:1', 'file': 'x2-test1-1-0001.bmp'}, 492 {'dev': 'test:0', 'file': 'x3-test0-2-0001.bmp-ccc'} 493 ] 494 495 idx = 1 496 for setup in setups: 497 print "scanning page #%s from device [%s]" % (idx, setup['dev']) 498 idx += 1 499 fnames = acquire_pages_into_files(device = setup['dev'], filename = setup['file'], delay = (idx*5)) 500 if fnames is False: 501 print "error, cannot acquire page" 502 else: 503 print " image files:", fnames 504 505 #================================================== 506 # $Log: gmScanBackend.py,v $ 507 # Revision 1.56 2009/12/21 15:02:51 ncq 508 # - cleanup 509 # 510 # Revision 1.55 2009/02/18 13:45:25 ncq 511 # - get_unique_filename API change 512 # 513 # Revision 1.54 2008/07/10 11:20:03 ncq 514 # - use --xsane-rc when calling XSane 515 # 516 # Revision 1.53 2008/04/11 12:24:01 ncq 517 # - better handle missing XSane when TWAIN is missing, too 518 # 519 # Revision 1.52 2007/12/12 16:17:15 ncq 520 # - better logger names 521 # 522 # Revision 1.51 2007/12/11 15:38:29 ncq 523 # - no more gmLog2 524 # 525 # Revision 1.50 2007/12/11 14:33:48 ncq 526 # - use standard logging module 527 # 528 # Revision 1.49 2007/09/10 20:27:40 ncq 529 # - eventually find out how xsane handles counter filenames 530 # 531 # Revision 1.48 2007/08/29 14:33:38 ncq 532 # - a bit more readability 533 # 534 # Revision 1.47 2007/08/08 21:26:39 ncq 535 # - tiny improvements re file extension in XSane scanner driver 536 # 537 # Revision 1.46 2007/07/17 13:40:31 ncq 538 # - PIL currently not used so don't load it 539 # 540 # Revision 1.45 2007/07/13 09:49:10 ncq 541 # - add missing "," 542 # 543 # Revision 1.44 2007/07/11 21:06:01 ncq 544 # - use gmTools.get_unique_filename() 545 # 546 # Revision 1.43 2007/07/10 20:37:56 ncq 547 # - properly delete the tempfile so XSane won't complain 548 # 549 # Revision 1.42 2007/07/09 12:38:38 ncq 550 # - cleanup 551 # 552 # Revision 1.41 2007/06/10 10:19:24 ncq 553 # - use exceptions for error reporting 554 # 555 # Revision 1.40 2007/06/05 14:58:16 ncq 556 # - better support missing XSane, thereby enabling better error reporting 557 # 558 # Revision 1.39 2007/05/08 11:14:34 ncq 559 # - cleanup 560 # 561 # Revision 1.38 2007/04/01 15:27:09 ncq 562 # - safely get_encoding() 563 # 564 # Revision 1.37 2007/02/17 18:18:09 ncq 565 # - support pre-setting device and device-settings-file with XSane 566 # 567 # Revision 1.36 2007/02/15 12:03:27 ncq 568 # - really support numbered multi-file scans with XSane 569 # 570 # Revision 1.35 2007/01/29 11:59:34 ncq 571 # - improve comment 572 # 573 # Revision 1.34 2007/01/19 14:06:17 ncq 574 # - do not attempt to handle several scans at once 575 # 576 # Revision 1.33 2007/01/19 13:37:39 ncq 577 # - cannot wait on semaphore as it blocks the TWAIN GUI 578 # 579 # Revision 1.32 2007/01/19 13:29:35 ncq 580 # - try semaphore on TWAIN scanning to detect finish 581 # 582 # Revision 1.31 2007/01/19 12:43:39 ncq 583 # - properly return False if initing scanner was cancelled 584 # 585 # Revision 1.30 2007/01/19 10:55:56 ncq 586 # - clean up 587 # 588 # Revision 1.29 2007/01/18 19:33:09 ncq 589 # - fix typo 590 # 591 # Revision 1.28 2007/01/18 18:43:07 ncq 592 # - added print "" for debugging 593 # 594 # Revision 1.27 2007/01/18 17:58:34 ncq 595 # - explicitely wait for TWAIN memory scan transfer to finish 596 # 597 # Revision 1.26 2007/01/18 17:14:54 ncq 598 # - closer to twain example code 599 # 600 # Revision 1.25 2007/01/18 14:41:29 ncq 601 # - use ShowModal in RequestAcquire() 602 # 603 # Revision 1.24 2007/01/18 13:27:16 ncq 604 # - try to comply more closely with TWAIN sample wx app 605 # 606 # Revision 1.23 2007/01/18 13:03:25 ncq 607 # - no ProductName 608 # 609 # Revision 1.22 2007/01/18 12:34:01 ncq 610 # - must init self.__scanner/self.__src_manager 611 # 612 # Revision 1.21 2007/01/18 12:08:56 ncq 613 # - try to once again fix/improve TWAIN scanning 614 # 615 # Revision 1.20 2006/12/27 16:42:53 ncq 616 # - cleanup 617 # - add XSane interface in cXSaneScanner as worked out by Kai Schmidt 618 # - acquire_pages_into_files() now returns a list 619 # - fix test suite 620 # 621 # Revision 1.19 2006/09/02 21:11:59 ncq 622 # - improved test suite 623 # 624 # Revision 1.18 2006/08/29 18:41:58 ncq 625 # - improve test suite 626 # 627 # Revision 1.17 2006/08/29 18:33:02 ncq 628 # - forward port TWAIN fixes from 0.2 branch 629 # 630 # Revision 1.16 2006/05/14 20:42:20 ncq 631 # - properly handle get_devices() 632 # 633 # Revision 1.15 2006/05/13 23:42:13 shilbert 634 # - getting there, TWAIN now lets me take more than one image in one session 635 # 636 # Revision 1.14 2006/05/13 23:18:11 shilbert 637 # - fix more TWAIN issues 638 # 639 # Revision 1.13 2006/05/13 21:36:15 shilbert 640 # - fixed some TWAIN rleated issues 641 # 642 # Revision 1.12 2006/01/17 19:45:32 ncq 643 # - close scanner when done 644 # - cleanup 645 # 646 # Revision 1.11 2006/01/16 19:42:18 ncq 647 # - improve unit test 648 # 649 # Revision 1.10 2006/01/16 19:41:29 ncq 650 # - properly init sane now 651 # 652 # Revision 1.9 2006/01/16 19:35:41 ncq 653 # - can only get sane device list after init() 654 # 655 # Revision 1.8 2006/01/16 19:27:26 ncq 656 # - cleaner layout 657 # - report_devices() -> get_devices() 658 # 659 # Revision 1.7 2006/01/15 14:00:28 ncq 660 # - reconvert spaces to tabs 661 # 662 # Revision 1.6 2006/01/15 13:16:06 shilbert 663 # - support for multiple scanners was added 664 # 665 # Revision 1.5 2006/01/15 10:04:37 shilbert 666 # - scanner device has not been passed on to the acquire_image function - fixed 667 # 668 # Revision 1.4 2005/11/27 13:05:45 ncq 669 # - used calling_window in the wrong place ... 670 # 671 # Revision 1.3 2005/11/27 10:38:46 ncq 672 # - use secure creation of file names when not given 673 # 674 # Revision 1.2 2005/11/27 08:48:45 ncq 675 # - some old cruft removed 676 # - example code useful being kept around for now commented out 677 # 678 # Revision 1.1 2005/11/26 16:53:43 shilbert 679 # - moved here from Archive 680 # - needed by gmScanIdxMedDocs plugin 681 # 682 # Revision 1.7 2005/11/09 11:30:21 ncq 683 # - activate sane test scanner 684 # 685