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

Source Code for Module AEMain

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