1
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
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
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
90
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
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
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
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
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
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
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
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
192 path, kind = options.action_value
193
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
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
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
218 options.action_value = path
219 rv = install(options)
220
221 options.action_value = name
222 options.profile = options.profile or GENERATE_PROFILES
223 return associate(options)
224
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
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
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
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
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
312
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
326 print _('Profiles')
327 print AccessEngine.AESettingsManager.listProfiles()
328 print
329
330
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
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
349 continue
350 a_count += len(names)
351 print kind.title()+'s'
352 print wrap(' '+', '.join(names))
353 elif kind == AERegistrar.SCRIPT:
354
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
361 a_count += len(names)
362 print ' (%s)' % app
363 print wrap(' '+', '.join(names))
364 print _('%d associated UIEs') % a_count
365 return ''
366
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
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
386
387 from SUEState import SUEState
388
389 rv = AccessEngine.AEMain.run(profile, not options.no_intro, SUEState())
390 return rv
391
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
411 pass
412 return _('Initialized default profiles')
413
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
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
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
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
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
480 chooser = AERegistrar.loadOne('A11yChooser')
481 chooser.init()
482
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
492 os.environ['GTK_MODULES'] = ''
493 else:
494
495 os.environ['GTK_MODULES'] = 'gail:atk-bridge'
496
497 import pygtk
498 pygtk.require('2.0')
499 import gtk
500
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
507 pass
508 else:
509 gtk.window_set_default_icon_list(*icons)
510
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
520 op = optparse.OptionParser()
521
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
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
597 options = parseOptions()
598
599 try:
600
601 method = options.action
602 except AttributeError:
603
604 method = run
605
606
607
608
609 if method is not initGlobal and not AccessEngine.AESettingsManager.hasDefaultProfiles():
610 initProfiles(None)
611
612 if method is not exportPath:
613
614 welcome()
615
616 if method is run:
617
618 configGUI(options)
619
620 configA11y(options)
621
622 AELog.configure(options.log_conf, options.log_level, options.log_channels,
623 options.log_file)
624
625 method(options)
626 else:
627 import traceback as tb
628 try:
629
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