Module SUEMain
[hide private]
[frames] | no frames]

Source Code for Module SUEMain

  1  #!/usr/bin/python 
  2  ''' 
  3  Defines the main function which parses command line arguments and acts on them. 
  4  Creates an instance of the L{AERegistrar} to add a new user interface element 
  5  (UIE) to the repository on disk when commands for the L{AERegistrar} are 
  6  present. If the kill switch is found, kills the running instance of SUE started 
  7  by this user. Otherwise, starts an instance of the L{AccessEngine} and runs the 
  8  screen reader. 
  9   
 10  The L{main} function in this module parses the command line parameters and 
 11  dispatches command line options to the other top level functions defined here. 
 12  The other top level functions split up the work of installing/uninstalling new 
 13  UIEs (L{install}, L{uninstall}), associating/unassociating (L{associate},  
 14  L{disassociate}), creating/removing/duplicating profiles (L{createProfile},  
 15  L{removeProfile}, L{duplicateProfile}), initializing the global list of  
 16  installed extensions L{initGlobal}, and running the screen reader (L{run}). 
 17  Other top level functions are provided for convenience such as L{configA11y}  
 18  and L{generate}. 
 19   
 20  #See the SUE man pages for help with using command line parameters. 
 21   
 22  @author: Peter Parente 
 23  @organization: IBM Corporation 
 24  @copyright: Copyright (c) 2005, 2007 IBM Corporation 
 25  @license: The BSD License 
 26   
 27  @author: Frank Zenker 
 28  @organization: IT Science Center Ruegen gGmbH, Germany 
 29  @copyright: Copyright (c) 2007, 2008 ITSC Ruegen 
 30  @license: The BSD License 
 31   
 32  All rights reserved. This program and the accompanying materials are made  
 33  available under the terms of the BSD license which accompanies 
 34  this distribution, and is available at  
 35  U{http://www.opensource.org/licenses/bsd-license.php} 
 36  ''' 
 37  try: 
 38    # little bit of fun to see how psyco improves things 
 39    import psyco 
 40    psyco.profile() 
 41  except ImportError: 
 42    pass 
 43   
 44  import Tools 
 45  import optparse, os, signal, sys, subprocess, glob, re 
 46  import AccessEngine 
 47  from AccessEngine import AERegistrar, AELog, AEOutput 
 48  from AccessEngine import AEConstants 
 49  from Tools.i18n import _ 
 50  import SUEConstants 
 51  from SUEConstants import DEFAULT_ASSOCIATIONS, DEFAULT_PROFILE, \ 
 52       GENERATE_PROFILES, PROG_VERSION, PROG_DATE, HOME_USER, HOME_DIR, \ 
 53       TEMPLATE_DIR 
 54   
 55  # define a regular expression for doing word wrap on the command line 
 56  wrap_rx=re.compile(u"([\u2e80-\uffff])", re.UNICODE) 
 57   
58 -def wrap(text, width=79, encoding='utf-8', indent=4):
59 ''' 60 A word-wrap function that preserves existing line breaks and most spaces in 61 the text. Expects that existing line breaks are posix newlines. Recipe by 62 Junyong Pan from the Python Cookbook: 63 64 U{http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/358117} 65 66 @param text: Text to wrap 67 @type text: string 68 @param width: Maximum width of a line 69 @type width: integer 70 @return: Wrapped text as a string 71 @rtype: string 72 ''' 73 return reduce(lambda line, word, width=width: '%s%s%s' % 74 (line, 75 [' ','\n'+' '*indent, ''][(len(line)-line.rfind('\n')-1 76 + len(word.split('\n',1)[0] ) >= width) or 77 line[-1:] == '\0' and 2], 78 word), 79 wrap_rx.sub(r'\1\0 ', unicode(text,encoding)).split(' ') 80 ).replace('\0', '').encode(encoding)
81
82 -def welcome():
83 ''' 84 Prints copyright, license, and version info. 85 ''' 86 print '%s ver. %s rev. %s' % \ 87 (SUEConstants.NAME, PROG_VERSION, PROG_DATE) 88 print SUEConstants.COPYRIGHT 89 print
90
91 -def confirmRemove(name):
92 ''' 93 Confirms deletion actions from the command prompt. Looks for a press of 'y' 94 to indicate confirmation. 95 96 @param name: Name of the "thing" being removed 97 @type name: string 98 @return: Confirm (True) or cancel (False)? 99 @rtype: boolean 100 ''' 101 ch = raw_input(SUEConstants.MSG_CONFIRM_REMOVE % name) 102 return ch == 'y'
103
104 -def install(options):
105 ''' 106 Installs a new UIE in the repository via the L{AERegistrar}. 107 108 @param options: Options seen on the command line 109 @type options: optparse.Values 110 @return: Description of the action 111 @rtype: string 112 @raise Exception: When the L{AERegistrar} reports an error completing the 113 install 114 ''' 115 reg = AERegistrar 116 reg.install(options.action_value, local=not options.uie_global) 117 return _('%s installed' % options.action_value)
118
119 -def uninstall(options):
120 ''' 121 Uninstalls a UIE from the repository via the L{AERegistrar}. 122 123 @param options: Options seen on the command line 124 @type options: optparse.Values 125 @return: Description of the action 126 @rtype: string 127 @raise Exception: When the L{AERegistrar} reports an error completing the 128 uninstall 129 ''' 130 # confirm deletion 131 if not confirmRemove(options.action_value): 132 return _('Cancelled uninstall of %s') % options.action_value 133 reg = AERegistrar 134 reg.uninstall(options.action_value, local=not options.uie_global) 135 return _('%s uninstalled' % options.action_value)
136
137 -def associate(options):
138 ''' 139 Associates a UIE with a profile so that it is loaded at a particular time 140 (e.g. on startup, when any tier is created, or when a particular tier is 141 created). 142 143 @param options: Options seen on the command line 144 @type options: optparse.Values 145 @return: Description of the action 146 @rtype: string 147 @raise Exception: When the L{AERegistrar} reports an error completing the 148 association 149 ''' 150 reg = AERegistrar 151 profiles = reg.associate(options.action_value, options.profile, 152 options.uie_tier, options.uie_all_tiers, 153 options.uie_index) 154 if not profiles: 155 return _('%s added to no profiles') % options.action_value 156 else: 157 return _('%s added to %s') % (options.action_value, ', '.join(profiles))
158
159 -def disassociate(options):
160 ''' 161 Disassociates a UIE with a profile so that it is no longer loaded at a 162 particular time. 163 164 @param options: Options seen on the command line 165 @type options: optparse.Values 166 @return: Description of the action 167 @rtype: string 168 @raise Exception: When the L{AERegistrar} reports an error completing the 169 disassociation 170 ''' 171 # confirm the deletion action 172 if not confirmRemove(options.action_value): 173 return _('Cancelled removal of %s') % options.action_value 174 reg = AERegistrar 175 profiles = reg.disassociate(options.action_value, options.profile, 176 options.uie_tier, options.uie_all_tiers) 177 return _('%s removed from %s' % (options.action_value, ', '.join(profiles)))
178
179 -def generate(options):
180 ''' 181 Generates a template file for the given kind of UIE. 182 183 @param options: Options seen on the command line 184 @type options: optparse.Values 185 @return: Description of the action 186 @rtype: string 187 @raise Exception: When there is a problem generating the new template because 188 not all required parameters were specified, the template does not exist, 189 the destination path is not writable, etc. 190 ''' 191 # get the path to the UIE to generate and the kind 192 path, kind = options.action_value 193 # figure out the name from the path 194 name, ext = os.path.basename(path).split(os.extsep) 195 if options.uie_tier is not None: 196 tier = "'%s'" % options.uie_tier 197 else: 198 tier = None 199 # fill in the template and copy it to the appropriate location 200 try: 201 temp = file(os.path.join(TEMPLATE_DIR, kind), 'r').read() 202 except IOError: 203 raise ValueError(_('No template for %s') % kind) 204 if options.uie_all_tiers: 205 desc = 'all applications' 206 elif tier: 207 desc = options.uie_tier 208 else: 209 desc = 'nothing' 210 # fill in the metadata for the new extension 211 uie = temp % dict(name=name, tier=tier, all_tiers=options.uie_all_tiers, 212 desc=desc) 213 try: 214 file(path, 'w').write(uie) 215 except IOError: 216 raise ValueError(_('Could not create %s file') % kind) 217 # install the UIE 218 options.action_value = path 219 rv = install(options) 220 # associate the UIE with the given profiles 221 options.action_value = name 222 options.profile = options.profile or GENERATE_PROFILES 223 return associate(options)
224
225 -def createProfile(options):
226 ''' 227 Creates a new, empty profile. 228 229 @param options: Options seen on the command line 230 @type options: optparse.Values 231 @return: Description of the action 232 @rtype: string 233 @raise Exception: When the L{AESettingsManager 234 <AESettingsManager._AESettingsManager>} reports an error creating the profile 235 ''' 236 AccessEngine.AESettingsManager.init(options.action_value) 237 AccessEngine.AESettingsManager.createProfile() 238 return _('Created profile %s') % options.action_value
239
240 -def duplicateProfile(options):
241 ''' 242 Duplicates an existing profile. 243 244 @param options: Options seen on the command line 245 @type options: optparse.Values 246 @return: Description of the action 247 @rtype: string 248 @raise Exception: When the L{AESettingsManager 249 <AESettingsManager._AESettingsManager>} reports an error copying the profile 250 ''' 251 AccessEngine.AESettingsManager(options.action_value[0]) 252 AccessEngine.AESettingsManager.copyProfile(options.action_value[1]) 253 return _('Duplicated profile %s as %s') % options.action_value
254
255 -def removeProfile(options):
256 ''' 257 Removes an existing profile. 258 259 @param options: Options seen on the command line 260 @type options: optparse.Values 261 @return: Description of the action 262 @rtype: string 263 @raise Exception: When the L{AESettingsManager 264 <AESettingsManager._AESettingsManager>} reports an error deleting the profile 265 ''' 266 AccessEngine.AESettingsManager(options.action_value) 267 AccessEngine.AESettingsManager.ensureProfile() 268 # confirm deletion 269 if not confirmRemove(options.action_value): 270 return _('Cancelled removal of profile %s') % options.action_value 271 AccessEngine.AESettingsManager.deleteProfile() 272 return _('Removed profile %s') % options.action_value
273
274 -def exportPath(options):
275 ''' 276 Gets the path to the package containing this file. Essentially, this is 277 the path to the SUE package on disk. 278 279 @param options: Options seen on the command line 280 @type options: optparse.Values 281 @return: Description of the action 282 @rtype: string 283 ''' 284 return os.path.abspath(os.path.normpath(os.path.dirname(__file__)))
285
286 -def say(options):
287 ''' 288 Uses the first available audio L{AEOutput} device to output a string. 289 290 @param options: Options seen on the command line 291 @type options: optparse.Values 292 @return: Description of the action 293 @rtype: string 294 ''' 295 if options.profile is None: 296 profile = DEFAULT_PROFILE 297 else: 298 profile = options.profile[0] 299 300 # build a partially initialized device manager 301 AccessEngine.AEDeviceManager.loadDevices() 302 303 # get an audio output device 304 try: 305 dev = AccessEngine.AEDeviceManager.getOutputByCaps(['audio']) 306 except Exception: 307 return _('No audio output device') 308 # output the utf-8 encoded string seen on the command line 309 s = unicode(options.action_value, 'utf-8') 310 AccessEngine.AEDeviceManager.send(dev, AEConstants.CMD_STRING_SYNC, s, None, None) 311 return _('Said: %s') % s
312
313 -def show(options):
314 ''' 315 Shows all installed scripts and profiles, indicating all associated UIE's 316 via the L{AERegistrar}. 317 318 @param options: Options seen on the command line 319 @type options: optparse.Values 320 @return: Description of the action 321 @rtype: string 322 ''' 323 reg = AERegistrar 324 if options.profile is None: 325 # list of profiles 326 print _('Profiles') 327 print AccessEngine.AESettingsManager.listProfiles() 328 print 329 330 # list installed UIEs if no profile is given 331 print _('Installed UIEs') 332 i_count = 0 333 for kind in AERegistrar.ALL_KINDS: 334 names = reg.listInstalled(kind) 335 i_count += len(names) 336 print kind.title()+'s' 337 print wrap(' '+', '.join(names)) 338 return _('%d installed UIEs') % i_count 339 else: 340 # list UIEs associated with each profile otherwise 341 for name in options.profile: 342 print _('Associated UIEs (%s)') % name 343 a_count = 0 344 for kind in AERegistrar.ALL_KINDS: 345 if kind in AERegistrar.STARTUP_KINDS: 346 names = reg.listAssociated(kind, name) 347 if not names: 348 # don't print a blank line if we have zero of a kind of UIE 349 continue 350 a_count += len(names) 351 print kind.title()+'s' 352 print wrap(' '+', '.join(names)) 353 elif kind == AERegistrar.SCRIPT: 354 # handle any tier scripts 355 print kind.title() 356 names = reg.listAssociatedAny(kind, name) 357 a_count += len(names) 358 print wrap(' '+', '.join(names)) 359 for app, names in reg.listAssociatedMap(kind, name).items(): 360 # then tier specific scripts 361 a_count += len(names) 362 print ' (%s)' % app 363 print wrap(' '+', '.join(names)) 364 print _('%d associated UIEs') % a_count 365 return ''
366
367 -def run(options):
368 ''' 369 Runs SUE. 370 371 @param options: Options seen on the command line 372 @type options: optparse.Values 373 @return: Description of the action 374 @rtype: string 375 @raise Exception: When anything goes wrong during initialization 376 ''' 377 import AccessEngine.AEAccAdapters 378 # make sure the profile exists 379 if options.profile is None: 380 profile = DEFAULT_PROFILE 381 else: 382 profile = options.profile[0] 383 if profile not in AccessEngine.AESettingsManager.listProfiles(): 384 raise ValueError(_('Profile %s does not exist') % profile) 385 # import OAE here so we can do other ops outside a Gnome session 386 387 from SUEState import SUEState 388 # use the profile specified on the command line or the default 389 rv = AccessEngine.AEMain.run(profile, not options.no_intro, SUEState()) 390 return rv
391
392 -def initProfiles(options):
393 ''' 394 Initializes the default user profiles using the L{AERegistrar}. All UIEs 395 shipped with SUE are associated with their appropriate profiles by this 396 method in a well-defined order specified here. 397 398 @param options: Options seen on the command line 399 @type options: optparse.Values 400 @return: Description of the action 401 @rtype: string 402 @raise Exception: When anything goes wrong during the initialization of the 403 default profiles 404 ''' 405 for profile, uies in DEFAULT_ASSOCIATIONS.items(): 406 for uie in uies: 407 try: 408 AERegistrar.associate(uie, profiles=[profile]) 409 except (AttributeError, KeyError), e: 410 # not a UIE or not installed; skip 411 pass 412 return _('Initialized default profiles')
413
414 -def initGlobal(options):
415 ''' 416 Initializes the global cache of installed UIEs in the L{HOME_DIR} folder 417 using the L{AERegistrar}. All UIEs in the Scripts, Devices, Choosers, and 418 Monitors are installed by this method. Any UIEs with names that do not 419 conflict with the defaults are left untouched by this process. 420 421 @param options: Options seen on the command line 422 @type options: optparse.Values 423 @return: Description of the action 424 @rtype: string 425 @raise ValueError: When anything goes wrong during initialization of the 426 global repository 427 ''' 428 uies = [] 429 for kind in AERegistrar.ALL_KINDS: 430 dir = kind.title()+'s' 431 432 if kind == AERegistrar.SCRIPT: 433 # account for script subdirs 434 path = os.path.join(HOME_DIR, dir) 435 for subdir in os.listdir(path): 436 subdir = os.path.join(HOME_DIR, dir, subdir) 437 if os.path.isdir(subdir): 438 path = os.path.join(subdir, '*.py') 439 uies.extend(glob.glob(path)) 440 else: 441 # other kinds of UIEs don't have subdirs 442 path = os.path.join(HOME_DIR, dir, '*.py') 443 uies.extend(glob.glob(path)) 444 for uie in uies: 445 try: 446 AERegistrar.install(uie, local=False, overwrite=True) 447 except AttributeError: 448 # not a UIE; skip 449 pass 450 return _('Initialized global extensions')
451
452 -def getAction(option, opt_str, value, parser):
453 ''' 454 Maps a command line param to the name of a function in this module that will 455 handle it. For instance, the command line parameter for installing a UIE is 456 mapped to the L{install} function which will carry out the command. 457 ''' 458 parser.values.action = option.default 459 parser.values.action_value = value
460
461 -def configA11y(options):
462 ''' 463 Runs gconf to check if desktop accessibility support is enabled or not. If 464 not, enables it and asks the user to logout. 465 466 @note: Current implementation is GNOME, AT-SPI dependent 467 @param options: Options seen on the command line 468 @type options: optparse.Values 469 @raise ValueError: When accessibility support is not enabled 470 ''' 471 if options.no_a11y: 472 return 473 try: 474 import gconf 475 except ImportError: 476 return 477 cl = gconf.client_get_default() 478 if not cl.get_bool('/desktop/gnome/interface/accessibility'): 479 # use the AERegistrar to load the A11yChooser 480 chooser = AERegistrar.loadOne('A11yChooser') 481 chooser.init()
482
483 -def configGUI(options):
484 ''' 485 Configures the GUI toolkit with a default icon list for all SUE top level 486 windows. Also enables accessibility for this application. 487 488 @note: Current implementation is gtk/gail dependent 489 ''' 490 if options.no_bridge: 491 # remove the bridge on purpose 492 os.environ['GTK_MODULES'] = '' 493 else: 494 # add GTK_MODULES to the environment so all GUIs are accessible 495 os.environ['GTK_MODULES'] = 'gail:atk-bridge' 496 497 import pygtk 498 pygtk.require('2.0') 499 import gtk 500 # load all sizes of icons 501 it = gtk.IconTheme() 502 try: 503 icons = [it.load_icon(SUEConstants.PROG_NAME, size, gtk.ICON_LOOKUP_NO_SVG) 504 for size in SUEConstants.ICON_SIZES] 505 except Exception: 506 # ignore errors, and just don't use the icon 507 pass 508 else: 509 gtk.window_set_default_icon_list(*icons)
510
511 -def parseOptions():
512 ''' 513 Parses the command line arguments based on the definition of the expected 514 arguments provided here. 515 516 @return: Parsed options 517 @rtype: optparse.Values 518 ''' 519 # create a command line option parser 520 op = optparse.OptionParser() 521 # add all the command line options to the parser 522 op.add_option('-g', '--generate', action='callback', callback=getAction, 523 help=SUEConstants.HELP_GENERATE, default=generate, type='str', 524 nargs=2) 525 op.add_option('-i', '--install', action='callback', callback=getAction, 526 help=SUEConstants.HELP_INSTALL, default=install, type='str', 527 nargs=1) 528 op.add_option('-u', '--uninstall', action='callback', callback=getAction, 529 help=SUEConstants.HELP_UNINSTALL, default=uninstall,type='str', 530 nargs=1) 531 op.add_option('-a', '--associate', action='callback', callback=getAction, 532 help=SUEConstants.HELP_ASSOCIATE, default=associate,type='str', 533 nargs=1) 534 op.add_option('-d', '--disassociate', action='callback', callback=getAction, 535 help=SUEConstants.HELP_DISASSOCIATE, default=disassociate, 536 type='str', nargs=1) 537 op.add_option('-c', '--create-profile', action='callback',callback=getAction, 538 help=SUEConstants.HELP_CREATE_PROFILE, default=createProfile, 539 type='str', nargs=1) 540 op.add_option('-r', '--remove-profile', action='callback',callback=getAction, 541 help=SUEConstants.HELP_REMOVE_PROFILE, default=removeProfile, 542 type='str', nargs=1) 543 op.add_option('--dupe', '--duplicate-profile', action='callback', 544 callback=getAction, default=duplicateProfile, 545 help=SUEConstants.HELP_DUPLICATE_PROFILE, type='str', nargs=2) 546 op.add_option('-s', '--show', action='callback', callback=getAction, 547 default=show, help=SUEConstants.HELP_SHOW) 548 op.add_option('-p', '--profile', action='append', dest='profile', 549 help=SUEConstants.HELP_PROFILE) 550 op.add_option('-y', '--say', action='callback', callback=getAction, 551 help=SUEConstants.HELP_SAY, default=say, type='str') 552 op.add_option('--index', dest='uie_index', action='store', type='int', 553 help=SUEConstants.HELP_INDEX) 554 op.add_option('--global', action='store_true', default=False, 555 dest='uie_global', help=SUEConstants.HELP_GLOBAL) 556 op.add_option('--all-tiers', '--all-apps', action='store_true', 557 default=False, 558 dest='uie_all_tiers', help=SUEConstants.HELP_ALL_TIERS) 559 op.add_option('--tier', '--app', dest='uie_tier', action='store', type='str', 560 help=SUEConstants.HELP_TIER) 561 op.add_option('-l', '--log-level', dest='log_level', action='store', 562 choices=['none', 'debug', 'print', 'info', 'warning', 'error'], 563 help=SUEConstants.HELP_LOG_LEVEL % '[debug, print, info, ' \ 564 'warning, error, critical]') 565 op.add_option('--log-channel', dest='log_channels', action='append', 566 help=SUEConstants.HELP_LOG_CHANNEL % '[Script, AETier, ...]') 567 op.add_option('--log-file', dest='log_file', action='store', type='str', 568 help=SUEConstants.HELP_LOG_FILENAME) 569 op.add_option('--log-conf', dest='log_conf', action='store', type='str', 570 help=SUEConstants.HELP_LOG_CONFIG) 571 op.add_option('--init-global', action='callback', callback=getAction, 572 default=initGlobal, help=SUEConstants.HELP_INIT_GLOBAL) 573 op.add_option('--init-profiles', action='callback', callback=getAction, 574 default=initProfiles, help=SUEConstants.HELP_INIT_PROFILES) 575 op.add_option('--no-intro', action='store_true', default=False, 576 dest='no_intro', help=SUEConstants.HELP_NO_INTRO) 577 op.add_option('--no-a11y', action='store_true', default=False, 578 dest='no_a11y', help=SUEConstants.HELP_NO_A11Y) 579 op.add_option('--no-bridge', action='store_true', default=False, 580 dest='no_bridge', help=SUEConstants.HELP_NO_BRIDGE) 581 op.add_option('--export-path', action='callback', callback=getAction, 582 default=exportPath, help=SUEConstants.HELP_EXPORT_PATH) 583 (options, args) = op.parse_args() 584 return options
585
586 -def main():
587 ''' 588 Print the welcome message, parses command line arguments, and configures the 589 logging system. Dispatches control to one of the top-level functions in this 590 module depending on the command line parameters specified. 591 592 If the SUE L{AccessEngine} should be run (i.e. not configured or tested), 593 checks that a11y support is enabled in gconf. If not, enables it but exits 594 with an error. 595 ''' 596 # parse the command line options 597 options = parseOptions() 598 599 try: 600 # see if an action has been specified 601 method = options.action 602 except AttributeError: 603 # if not, run SUE 604 method = run 605 606 # check if the default profiles need to be created 607 # but don't do it if we're initializing the global repo since we might be 608 # the root user 609 if method is not initGlobal and not AccessEngine.AESettingsManager.hasDefaultProfiles(): 610 initProfiles(None) 611 612 if method is not exportPath: 613 # show the welcome message 614 welcome() 615 616 if method is run: 617 # configure GUI toolkit 618 configGUI(options) 619 # make sure accessibility is enabled on the desktop and for SUE 620 configA11y(options) 621 # set up the logging system 622 AELog.configure(options.log_conf, options.log_level, options.log_channels, 623 options.log_file) 624 # run the AccessEngine 625 method(options) 626 else: 627 import traceback as tb 628 try: 629 # execute the function for the action 630 print method(options) 631 except Exception, e: 632 info = tb.extract_tb(sys.exc_info()[2]) 633 print '%s (%d): %s' % (os.path.basename(info[-1][0]), info[-1][1], e)
634 635 if __name__ == '__main__': 636 main() 637