1 '''
2 Defines a class responsible for managing all input and output devices and
3 monitors.
4
5 @author: Larry Weiss
6 @author: Peter Parente
7 @author: Brett Clippingdale
8 @author: Scott Haeger
9 @organization: IBM Corporation
10 @copyright: Copyright (c) 2005, 2007 IBM Corporation
11 @license: The BSD License
12
13 All rights reserved. This program and the accompanying materials are made
14 available under the terms of the BSD license which accompanies
15 this distribution, and is available at
16 U{http://www.opensource.org/licenses/bsd-license.php}
17 '''
18 import logging, sys
19 import AEConstants, AEOutput, AEInput, AEEvent, AEMonitor, UIRegistrar
20 from AEInterfaces import implements
21 from UIRegistrar import DEVICE, MONITOR
22 from i18n import _
23
24 log = logging.getLogger('Device')
25
27 '''
28 Creates and manages the devices and monitors use by LSR. Keeps a list of each
29 and also defines a "default" for devices. Provides a common interface to all
30 output and input devices and mirrors I/O to the monitors. Provides a mapping
31 from output styles to semantic concepts in the user interface to allow
32 customization of how information is presented.
33
34 @ivar event_manager: L{EventManager} to which this L{DeviceManager} can post
35 L{AEEvent}s
36 @type event_manager: L{EventManager}
37 @ivar out_devs: All output devices registered and initialized
38 @type out_devs: list
39 @ivar in_devs: All input devices registered and initialized
40 @type in_devs: list
41 @ivar in_mons: Collection of monitors to notify about input
42 @type in_mons: L{AEMonitor.MonitorCollection}
43 @ivar out_mons: Collection of monitors to notify about output
44 @type out_mons: L{AEMonitor.MonitorCollection}
45 @ivar acc_eng: The AccessEngine reference that created this class
46 @type acc_eng: L{AccessEngine}
47 @ivar marks: Maps index markers on devices to
48 @type marks: dictionary
49 @ivar temp_devs: List of L{AEOutput} and L{AEInput} devices last removed in
50 L{unloadDevices} and held until the next call of L{loadDevices}. The
51 purpose of this list is to ensure strong references to all devices exist,
52 and thus weak references are not destroyed, until the the L{DeviceManager}
53 is repopulated with devices.
54 @type temp_devs: list
55 '''
57 '''
58 Creates the empty lists for devices and monitors.
59
60 @param acc_eng: The AccessEngine reference that created this class
61 @type acc_eng: L{AccessEngine}
62 '''
63 self.out_devs = []
64 self.in_devs = []
65 self.in_mons = AEMonitor.MonitorCollection()
66 self.out_mons = AEMonitor.MonitorCollection()
67 self.acc_eng = acc_eng
68 self.event_manager = None
69 self.settings_manager = None
70 self.marks = {}
71 self.temp_devs = []
72
73 - def init(self, event_man, sett_man, **kwargs):
74 '''
75 Called by L{AccessEngine} at startup.
76
77 @param event_man: L{EventManager} to which this L{DeviceManager} can
78 post L{AEEvent}s
79 @type event_man: L{EventManager}
80 @param sett_man: L{SettingsManager} which this L{DeviceManager} can use to
81 load and persist device settings
82 @type sett_man: L{EventManager}
83 @param kwargs: References to managers not of interest here
84 @type kwargs: dictionary
85 '''
86 self.event_manager = event_man
87 self.settings_manager = sett_man
88
89
90 mons = UIRegistrar.loadAssociated(MONITOR, self.acc_eng.getProfile())
91 self.addMonitors(*mons)
92
93
94 self.loadDevices()
95
97 '''
98 Iterates over all devices in the active profile and loads ones that provide
99 functionality different from those already loaded.
100 '''
101 devs = UIRegistrar.loadAssociated(DEVICE, self.acc_eng.getProfile())
102
103 for d in devs:
104
105 try:
106 self.registerDevice(d)
107 except (AEOutput.AEOutputError, AEInput.AEInputError):
108 log.debug('could not initialize device: %s', d.getName())
109 except NotImplementedError, e:
110 log.debug('device %s does not provide a new interface: %s' %
111 (d.getName(), str(e)))
112
113
114 if not len(self.out_devs):
115 log.warn(_('no output available'))
116 if not len(self.in_devs):
117 log.warn(_('no input available'))
118 self.temp_devs = []
119
121 '''
122 Unloads all devices from L{out_devs} and L{in_devs}, but keeps strong
123 references to all objects in L{temp_devs} until L{loadDevices} is called
124 again. Keeping strong references ensures weak references in L{Perk}s
125 continue to exist until the L{DeviceManager} has new devices available
126 for I/O.
127
128 '''
129 for d in self.out_devs[:]:
130 self.temp_devs.append(d)
131 self.unregisterDevice(d)
132
133
134
136 '''
137 Shuts down this manager and all its registered L{AEOutput} and L{AEInput}
138 devices and L{AEMonitor}s. Saves all style information for currently
139 loaded devices.
140 '''
141 for d in self.out_devs[:]:
142 self.unregisterDevice(d)
143 for d in self.in_devs[:]:
144 self.unregisterDevice(d)
145 self.in_mons.clear()
146 self.out_mons.clear()
147
148 - def send(self, dev, name, value, sem, layer):
149 '''
150 Sends arbitrary data to a device. The device must recognize the name in
151 order to decide what to do with the value data.
152
153 This is a generic method which receives all content and commands to be
154 rendered and executed on a device. Standard name identifiers should be
155 used whenever possible. For instance, L{AEConstants.Output.CMD_STOP} and
156 L{AEConstants.Output.CMD_TALK}. However, device specific names and values
157 are certainly possible.
158
159 @param name: Descriptor of the data value sent
160 @type name: object
161 @param value: Content value
162 @type value: object
163 @param sem: The semantic stream on which to send output; defaults to None
164 for the default semantic.
165 @type sem: integer
166 @param layer: Layer on which the event occurred
167 @type layer: integer
168 @param dev: Device that should receive the command
169 @type dev: L{AEOutput}
170 @return: Return value specific to the given command
171 @rtype: object
172 '''
173
174 self.out_mons.show(OutputEvent(name), value=value, dev=dev, sem=sem,
175 layer=layer)
176
177 if dev is None: return
178 if sem is None or layer is None:
179 try:
180 return dev.send(name, value, None)
181 except NotImplementedError:
182 return None
183 else:
184 style = dev.getStyle((sem, layer))
185
186 if style.Mute: return
187
188 try:
189 rv = dev.send(name, value, style)
190 except NotImplementedError:
191
192 rv = None
193
194 style.makeClean()
195 return rv
196
198 '''
199 Sends the stop command to the referenced output device.
200
201 @param dev: Device that should receive the command
202 @type dev: L{AEOutput}
203 @param sem: Semantic description of the information to stop None to
204 indicate stopping all output
205 @type sem: integer
206 @param layer: Layer on which the event occurred
207 @type layer: integer
208 '''
209
210 self.out_mons.show(OutputEvent(AEConstants.CMD_STOP), dev=dev, sem=sem,
211 layer=layer)
212 if dev is None: return
213 if sem is None and layer is None:
214
215 dev.send(AEConstants.CMD_STOP, None, None)
216 elif sem is None:
217
218 all = set([dev.getStyle((sem, layer)) for sem in
219 AEConstants.SEMANTIC_STYLES.keys()])
220 for style in all:
221 dev.send(AEConstants.CMD_STOP, None, style)
222 else:
223 style = dev.getStyle((sem, layer))
224 dev.send(AEConstants.CMD_STOP, None, style)
225
227 '''
228 Tells the specified output device to send buffered data.
229
230 @param dev: Device that should receive the command
231 @type dev: L{AEOutput}
232 @param sem: Semantic information to start outputting or None to indicate
233 that all buffered information should be output
234 @type sem: integer
235 @param layer: Layer on which the event occurred
236 @type layer: integer
237 '''
238
239 self.out_mons.show(OutputEvent(AEConstants.CMD_TALK), dev=dev, sem=sem,
240 layer=layer)
241 if dev is None: return
242 if sem is None:
243 style = None
244 else:
245 style = dev.getStyle((sem, layer))
246
247 dev.send(AEConstants.CMD_TALK, None, style)
248
250 '''
251 Sends the filename to the specified output device.
252
253 @param dev: Device that should receive the command
254 @type dev: L{AEOutput}
255 @param filename: The filename to send to the referenced device
256 @type filename: string
257 @param sem: Semantic description of the text to send or None to indicate the
258 information is void of any known semantic
259 @type sem: integer
260 @param layer: Layer on which the event occurred
261 @type layer: integer
262 '''
263
264 self.out_mons.show(OutputEvent(AEOutput.NAME_FILENAME), value=filename,
265 dev=dev, sem=sem, layer=layer)
266 if dev is None: return
267
268 style = dev.getStyle((sem, layer))
269
270 if style.Mute: return
271 try:
272 dev.send(AEConstants.CMD_FILENAME, filename, style)
273 except NotImplementedError:
274 pass
275
276 style.makeClean()
277
278 - def sendString(self, dev, text, sem, layer, por=None):
279 '''
280 Sends the string to the specified output device.
281
282 @param dev: Device that should receive the command
283 @type dev: L{AEOutput}
284 @param text: The text to send to the referenced device
285 @type text: string
286 @param sem: Semantic description of the text to send or None to indicate
287 the information is void of any known semantic
288 @type sem: integer
289 @param layer: Layer on which the event occurred
290 @type layer: integer
291 @param por: Point of regard to the start of the string if one exists or
292 None if the string did not originate from a L{POR}
293 @type por: L{POR}
294 '''
295
296 self.out_mons.show(OutputEvent(AEConstants.CMD_STRING), value=text,
297 dev=dev, sem=sem, layer=layer)
298 if dev is None: return
299
300 style = dev.getStyle((sem, layer))
301
302 if style.Mute: return
303
304 parsed = dev.parseString(text, style, por, sem)
305
306 for word, por, new_style in parsed:
307 dev.send(AEConstants.CMD_STRING, word, new_style)
308
309 new_style.makeClean()
310
311 style.makeClean()
312
313 - def sendIndex(self, dev, value, sem, layer):
314 '''
315 Sends the referenced index marker to the referenced device.
316
317 @param dev: Device that should receive the command
318 @type dev: L{AEOutput}
319 @param sem: Semantic description of the index text to send or None to
320 indicate the information is void of any known semantic
321 @type sem: integer
322 @param layer: Layer on which the event occurred
323 @type layer: integer
324 '''
325
326 self.out_mons.show(OutputEvent(AEConstants.CMD_INDEX), dev=dev, sem=sem,
327 layer=layer)
328 if dev is None: return
329
330 style = dev.getStyle((sem, layer))
331
332 if style.Mute: return
333 try:
334
335 mark = dev.send(AEConstants.CMD_INDEX, None, style)
336 except NotImplementedError:
337
338 return
339
340
341
354
364
366 '''
367 Checks if the given device is a duplicate in that it provides no new
368 interfaces beyond those provided by the devices already registered.
369
370 @param dev: A device class
371 @type dev: L{AEInput} or L{AEOutput} class
372 @param output: Is the device an output device (True) or input (False)?
373 @type output: boolean
374 '''
375 if output:
376 reg_devs = self.out_devs
377 else:
378 reg_devs = self.in_devs
379
380 dev_int = dev.getCapabilities()
381
382
383 for reg_dev in reg_devs:
384 reg_int = reg_dev.getCapabilities()
385 if set(reg_int).issuperset(dev_int):
386 return True
387 return False
388
419
439
441 '''
442 Unregisters the given output device. Provides the device with a reference
443 to the L{SettingsManager} so it can save state. Removes this manager from
444 the list of index listeners stored in the device.
445
446 @param dev: Device to unregister
447 @type dev: L{AEOutput.AEOutput}
448 '''
449 try:
450
451 dev.saveStyles(self.settings_manager)
452 except AttributeError:
453 pass
454 try:
455
456 self.out_devs.remove(dev)
457 log.debug('removed output device %s', dev.getName())
458 except ValueError:
459 pass
460 try:
461
462 dev.removeIndexListener(self._onIndex)
463 except AttributeError:
464 pass
465
467 '''
468 Registers the given device as an output device if the device implements the
469 L{AEOutput} base class and provides some subinterface not already provided
470 by another registered output device. For instance, if a device reports
471 having "audio" capability, no other device providing just this capability
472 will be loaded.
473
474 @param dev: Device to attempt to register
475 @type dev: L{AEOutput.AEOutput}
476 @return: True if the device was registered, False if it is not an
477 L{AEOutput} device, and None if it provides no new interfaces
478 @rtype: boolean or None
479 '''
480 if not implements(dev, AEOutput.AEOutput):
481
482 return False
483 dev = dev.getProxy()
484 if self._isDuplicateDevice(dev, True):
485
486
487 return None
488 if dev not in self.in_devs:
489
490 dev.init()
491
492 try:
493 dev.addIndexListener(self._onIndex)
494 except (AttributeError, NotImplementedError):
495
496 pass
497
498 self.out_devs.append(dev)
499 log.debug('added output device %s', str(dev))
500
501
502 try:
503
504 dev.loadStyles(self.settings_manager)
505 except KeyError:
506
507 self._initOutputStyles(dev)
508
509
510 dev.postInit()
511 return True
512
514 '''
515 Initializes styles for an L{AEOutput} device. Calls
516 L{AEOutput.Base.AEOutput.createDistinctStyles} on the device to get an
517 initial batch of styles to use to distinguish some types of information. If
518 that method is not implemented, the exception is ignored. Future requests
519 to use styles per semantic tag will resort to making flyweights for the
520 default style on the device.
521
522 @param dev: Device reference
523 @type dev: L{AEOutput}
524 '''
525
526 num_layers = len(AEConstants.LAYERS_ALL)
527 num_groups = len(AEConstants.STYLE_GROUP_ALL)
528 try:
529 styles = dev.createDistinctStyles(num_groups, num_layers)
530 except NotImplementedError:
531 return
532
533 for layer, i in enumerate(AEConstants.LAYERS_ALL):
534
535 s = styles[i*num_groups]
536 dev.setStyle((None, layer), s)
537 for sem, grp in AEConstants.SEMANTIC_STYLES.items():
538
539 s = styles[i*num_groups + (grp % num_groups)]
540 dev.setStyle((sem, layer), s)
541
543 '''
544 Registers the referenced device based on its one or more interfaces.
545 When the interface is determined, the init() method is called on the device.
546 Returns true when the reference is of a known type that initializes
547 successfully.
548
549 @param dev: The device to register
550 @type dev: L{AEOutput} or L{AEInput} class
551 @raise NotImplementedError: When the device implements none of the required
552 interfaces
553 @raise AEOutputError: When output device initialization fails
554 @raise AEInputError: When input device initialization fails
555 '''
556 is_out = self._registerOutputDevice(dev)
557 is_in = self._registerInputDevice(dev)
558
559 if is_out == False and is_in == False:
560
561 raise NotImplementedError
562
564 '''
565 Unregisters a device from both the input and output lists based on its
566 capabilities.
567
568 @param dev: The device to unregister
569 @type dev: L{AEOutput} or L{AEInput} class
570 '''
571 try:
572 dev.close()
573 except Exception:
574 pass
575 self._unregisterOutputDevice(dev)
576 self._unregisterInputDevice(dev)
577
579 '''
580 Adds one or more L{AEMonitor}s to the list of monitors to be notified about
581 IO events.
582
583 @param monitors: L{AEMonitor}s to notify
584 @type monitors: tuple of L{AEMonitor}s
585 '''
586 self.in_mons.add(InputEvent, monitors)
587 self.out_mons.add(OutputEvent, monitors)
588
590 '''
591 @return: Collections of all loaded input and output L{AEMonitor}s in that
592 order
593 @rtype: 2-tuple L{AEMonitor.MonitorCollection}
594 '''
595 return self.in_mons, self.out_mons
596
598 '''
599 Gets the first L{AEOutput} device to successfully load.
600
601 @return: First loaded output device or None if no output is available
602 @rtype: L{AEOutput}
603 '''
604 try:
605 return self.out_devs[0]
606 except IndexError:
607 return None
608
610 '''
611 Gets the L{AEOutput} device registered under the given name.
612
613 @param name: Name of the output device
614 @type name: string
615 @return: Output device or None if not registered
616 @rtype: L{AEOutput}
617 '''
618 for dev in self.out_devs:
619 if dev.getClassName() == name:
620 return dev
621 return None
622
624 '''
625 Gets the L{AEOutput} device registered with the given capabilities.
626
627 @param caps: Desired capabilities of the output device.
628 @type caps: list of string
629 @return: Output device or None if caps could not be satisfied
630 @rtype: L{AEOutput}
631 '''
632 for dev in self.out_devs:
633 if set(dev.getCapabilities()).issuperset(caps):
634 return dev
635 return None
636
650
664
665 - def _onGesture(self, gesture, timestamp, **kwargs):
666 '''
667 Creates an L{AEEvent} indicating the given gesture was found on a
668 registered input device. When executed, the L{AEEvent} will notify the
669 L{TierManager} about the gesture and allow it to activate the appropriate
670 L{Task} registered to respond to the gesture in the active L{Tier}.
671
672 @param gesture: Gesture seen on an input device
673 @type gesture: L{AEInput.Gesture}
674 @param timestamp: Time at which the event occurred
675 @type timestamp: float
676 @param kwargs: Additional keyword arguments to pass to the L{Task}
677 @type kwargs: dictionary
678 '''
679 event = AEEvent.InputGesture(gesture, timestamp, **kwargs)
680 self.event_manager.postEvents(event)
681
682 self.in_mons.show(InputEvent(AEConstants.CMD_GESTURE), value=gesture,
683 dev=gesture.getDevice())
684
687
688
689
691 '''
692 @return: References to all loaded devices
693 @rtype: list of L{UIElement}
694 '''
695 return list(set(self.in_devs).union(self.out_devs))
696
698 '''
699 Gets the style for semantic/layer of a given device.
700
701 @param dev: The device from which to get the style.
702 @type dev: L{AEOutput}
703 @param sem: The device semantic from which to get the style, or None to
704 get the device's default style.
705 @type sem: integer
706 @param layer: Layer on which the event occurred
707 @type layer: integer
708 @return: the L{AEOutput.Style} for the given semantic of a device
709 @rtype: L{AEOutput.Style}
710 @raise KeyError: When the semantic or layer is invalid
711 '''
712 if dev is None: return None
713 if sem is None or layer is None:
714 return dev.getDefaultStyle()
715 return dev.getStyle((sem, layer))
716
718 '''
719 Base type for indicating IO events to monitors such that a monitor may
720 select whether to monitor input, output, both, or neither based on this
721 type and its subtypes.
722
723 The L{cmd} attribute of an instance of this class should be considered
724 public readable.
725
726 @ivar cmd: Command constant for this event
727 @type cmd: object
728 '''
730 '''
731 Stores the command constant.
732
733 @param cmd: Command constant for this event
734 @type cmd: object
735 '''
736 self.cmd = cmd
737
746
748 '''Event on an output device, wrapped for display by a L{AEMonitor}.'''
755