1 '''
2 Defines a L{Perk} class that can register L{Task}s which will be executed in
3 response to L{AEEvent}s and L{AEInput.Gesture}s.
4
5 @author: Peter Parente
6 @author: Pete Brunet
7 @organization: IBM Corporation
8 @copyright: Copyright (c) 2005, 2007 IBM Corporation
9 @license: The BSD License
10
11 All rights reserved. This program and the accompanying materials are made
12 available under the terms of the BSD license which accompanies
13 this distribution, and is available at
14 U{http://www.opensource.org/licenses/bsd-license.php}
15 '''
16 import logging, weakref, os, sys
17 import Task, AEInput, AEEvent, AEInterfaces, UIElement, AEConstants
18 from i18n import _
19 from AEState import AEState as PerkState
20
21 log = logging.getLogger('Perk')
22
23 -def _postTimerEvent(interval, tid, aid, wtask, weman):
24 '''
25 Callback for timer events. Tries to post an L{AEEvent} for the L{Task}
26 intended to handle the event if the L{Task} is still alive.
27
28 @param interval: Interval in seconds on which the timer fires
29 @type interval: interval
30 @param tid: ID of the L{Task} to execute in response to the
31 L{AEEvent.TimerAlert}
32 @type tid: integer
33 @param wtask: Weak reference to the L{Task} to execute. Used to determine if
34 the L{Task} is still alive. If not, the timer is unregistered.
35 @type wtask: weakref.ref
36 @param weman: Weak reference to the L{EventManager} to use to post
37 L{AEEvent.TimerAlert} events.
38 @type weman: weakref.proxy
39 @return: Continue timer alerts on the interval (True) or stop them (False).
40 @rtype: boolean
41 '''
42 if not wtask():
43 return False
44 try:
45 weman.postEvents(AEEvent.TimerAlert(aid, tid, interval))
46 except ReferenceError:
47 return False
48 return True
49
50 -class Perk(Task.Tools.All, UIElement.UIE):
51 '''
52 Registers and maintains a collection of L{Task}s that will execute in
53 response to L{AccessEngine} events and commands (L{AEEvent}s and
54 L{AEInput.Gesture}s.). This class should be sublclassed by L{Perk} developers
55 who wish to define, modify, or extend the LSR user interface. This class
56 derives from L{Task.Tools.All} to allow the use of the convenience methods
57 defined in that class during L{Perk} intialization.
58
59 @cvar STATE: Class to instantiate and use to store state information across
60 all instances of this L{Perk}. Defaults to the L{AEState} base class, but
61 can be overridden with a subclass of L{AEState}.
62 @type STATE: L{AEState.AEState} class
63 @ivar named_tasks: L{Task}s registered by name
64 @type named_tasks: dictionary string : Task instance
65 @ivar keyed_tasks: L{Task}s registered by unique integer ID
66 @type keyed_tasks: dictionary integer : Task instance
67 @ivar event_tasks: L{Task}s registered by type
68 @type event_tasks: dictionary of (Task subclass : list of Task instance)
69 @ivar commands: Name of a L{Task} to execute in response to a
70 L{AEInput.GestureList} sequence on some L{AEInput} device. The name should
71 be hashed into the L{named_tasks} dictionary to fully resolve the L{Task}.
72 @type commands: weakref.WeakValueDictionary of (GestureList : string)
73 @ivar state: Settings for this L{Perk}. Defaults to an empty L{PerkState}
74 object that can be used to store state information that is not
75 configurable or persistable unless L{STATE} is specified.
76 @type state: L{PerkState}
77 @ivar out_caps: Capabilities set by L{setIdealOutput} as ideal for output
78 from this L{Perk}. These are used to "late bind" a device for use during
79 execution of this L{Perk} and its L{Task}s. Defaults to audio.
80 @type out_caps: list of string
81 @ivar before_chains: Mapping from target L{Task} name to a list of L{Task}
82 names which should be executed before the target
83 @type before_chains: dictionary of {string : list of string}
84 @ivar after_chains: Mapping from target L{Task} name to a list of L{Task}
85 names which should be executed after the target
86 @type after_chains: dictionary of {string : list of string}
87 @ivar around_chains: Mapping from target L{Task} name to one L{Task} name
88 which should be executed instead of the target
89 @type around_chains: dictionary of {string : list of string}
90 @ivar registered_modifiers: Mapping from device to a registered modifier for
91 that device
92 @type registered_modifiers: weakref.WeakKeyDictionary
93 '''
94 STATE = PerkState
95
97 '''
98 Creates empty dictionaries for named, event, command, and chooser L{Task}s.
99 Create a state object for storage using the class in L{STATE} in case the
100 L{Perk} hasn't previously persisted its settings on disk.
101
102 @note: L{Perk} writers should override L{init} to do initialization, not
103 this method.
104 '''
105 super(Perk, self).__init__(*args, **kwargs)
106 self.named_tasks = {}
107 self.keyed_tasks = {}
108 self.event_tasks = {}
109 self.commands = {}
110
111 self.out_caps = ['audio']
112
113 self.before_chains = {}
114 self.after_chains = {}
115 self.around_chains = {}
116 self.registered_modifiers = weakref.WeakKeyDictionary()
117
118
119
120 self.state = self.STATE()
121
122 self.state.init()
123
124 - def preInit(self, ae, *args, **kwargs):
125 '''
126 Overrides pre-initialization to load persisted state from disk. Fetches the
127 managers from the L{AccessEngine} first so that state can be loaded using
128 the L{SettingsManager}. Loads the state, then invokes the super class
129 version of this method to finish initialization.
130
131 @note: L{Perk} writers should override L{init} to do initialization, not
132 this method.
133 @param ae: Reference to AccessEngine for event context
134 @type ae: L{AccessEngine}
135 '''
136
137
138 ae.loanManagers(self)
139 try:
140
141 self.state = self.sett_man.loadState(self.getClassName(), self.state)
142 except KeyError:
143
144 pass
145
146 super(Perk, self).preInit(ae, *args, **kwargs)
147
148 - def postClose(self):
149 '''
150 Frees all L{Task}s managed by this L{Perk}.
151
152 @note: L{Perk} writers should override L{close} to do finialization, not
153 this method.
154 '''
155
156 super(Perk, self).postClose()
157
158 for tasks in self.event_tasks.values():
159 map(Task.Task.close, tasks)
160 map(Task.Task.postClose, tasks)
161 for tasks in (self.named_tasks.values(), self.keyed_tasks.values()):
162 map(Task.Task.close, tasks)
163 map(Task.Task.postClose, tasks)
164
165 for dev, codes in self.registered_modifiers.iteritems():
166 self.unregisterModifiers(dev, codes)
167
168
169 self.named_tasks = {}
170 self.keyed_tasks = {}
171 self.event_tasks = {}
172 self.commands = {}
173 self.registered_modifiers = weakref.WeakKeyDictionary()
174
176 '''
177 Sets the ideal device capabilities to be used to locate a device for all
178 output methods invoked from the caller's L{Perk} module, including the
179 Perk class itself and all of its registered L{Task}s. The capabilities list
180 should include strings naming L{AEOutput} interfaces ("audio" and/or
181 "braille") at present.
182
183 @param capabilities: Names of capabilities required on the device
184 @type capabilities: list of string
185 '''
186 self.out_caps = capabilities
187
189 '''
190 Gets the ideal device capabilities to locate a device to be used by all
191 output methods invoked from the caller's L{Perk} module, including the Perk
192 class itself and all of its registered L{Task}s.
193
194 @return: Names of capabilities set as ideal for a default output device
195 @rtype: list of string
196 '''
197 return self.out_caps
198
200 '''
201 @return: Returns the settings for this L{Perk} or a dummy object that can
202 be used to store state information that is neither user configurable or
203 persistable
204 @rtype: L{AEState.AEState}
205 '''
206 return self.state
207
209 '''
210 Initializes a L{Task} by calling L{Task.Base.Task.preInit} and
211 L{Task.Base.Task.init} if it has not already been initialized.
212
213 @param task: L{Task} to initialize
214 @type task: L{Task}
215 '''
216 if task.preInit(self.acc_eng, self.tier, self):
217 task.init()
218
220 '''
221 Registers a L{Task} to be called on a set interval.
222
223 @param interval: Interval in seconds which the L{Task} will be notified
224 @type interval: integer
225 '''
226 tid = id(task)
227 aid = self.tier.getIdentity()
228 wtask = weakref.ref(task)
229 weman = weakref.proxy(self.event_man)
230 self._initTask(task)
231 self.keyed_tasks[tid] = task
232
233 self.acc_eng.addTimer(_postTimerEvent, interval, interval, tid, aid,
234 wtask, weman)
235 self.tier.addTaskRef(tid, self)
236
238 '''
239 Registers a L{Task} in this L{Tier} to be executed in response to an
240 L{AEEvent} indicating that the given action codes were input on the given
241 L{AEInput} device.
242
243 @param device: Input device to monitor
244 @type device: L{AEInput}
245 @param codes: List of lists of action codes forming the L{AEInput.Gesture}
246 that will trigger the execution of the named L{Task}. For example,
247 codes=[[Keyboard.AEK_CTRL, Keyboard.AEK_TILDE]] indicates the single
248 gesture of simultaneously pressing Ctrl and ~ on the keyboard device.
249 @type codes: list of list of integer
250 @param name: Name of the L{Task} registered via L{registerNamedTask} to
251 execute when the input gesture is detected on the device
252 @type name: string
253 @param propagate: Should the input gesture be allowed to propagate to the
254 OS after we receive it?
255 @type propagate: boolean
256 @raise ValueError: When a L{Task} with the given name is not registered
257 '''
258
259 gl = AEInput.GestureList(device, codes)
260 if self.tier.getCommandTask(gl) is not None:
261
262
263 raise ValueError(
264 'command already registered with action codes %s on device %s' %
265 (codes, device.getName()))
266
267
268 self.commands[gl] = name
269 self.tier.addTaskRef(gl, self)
270 if not propagate:
271
272 try:
273 device.addFilter(gl)
274 except NotImplementedError:
275 pass
276
277 try:
278 device.addKeyCmd(codes)
279 except NotImplementedError:
280 pass
281
283 '''
284 Registers a L{Task} with the given name to be executed in response to a
285 change in the given L{AEChooser}.
286
287 @param chooser: Chooser that the L{Task} should observe
288 @type chooser: L{AEChooser}
289 @param task: Instance of a L{Task} that should observe the chooser
290 @type task: L{Task.ChooserTask}
291 '''
292 cid = id(chooser)
293 if self.tier.getKeyedTask(cid) is not None:
294 raise ValueError('Chooser Task already registered')
295 self._initTask(task)
296 self.keyed_tasks[cid] = task
297 self.tier.addTaskRef(cid, self)
298
300 '''
301 Registers a new L{Task} under the given name if no L{Task} is already
302 registered with that name in this L{Tier}.
303
304 Only one L{Task} can be registered under a name in a L{Tier}. If a L{Task}
305 is already registered under the given name, any other registration with
306 that name is ignored.
307
308 @param task: L{Task} to register
309 @type task: L{Task.Base.Task}
310 @param name: Name to associate with the L{Task}
311 @type name: string
312 @raise ValueError: When a L{Task} with the given name is already registered
313 in this L{Tier}
314 '''
315 if self.tier.getNamedTask(name) is None:
316
317 self._initTask(task)
318 self.named_tasks[name] = task
319 self.tier.addTaskRef(name, self)
320 else:
321 raise ValueError(name)
322
324 '''
325 Registers a new L{Task} in this L{Perk} under the L{Task} type. The type
326 determines which kind of L{AEEvent} will trigger the execution of the
327 registered L{Task}. If one or more Tasks are already registered for this
328 type, the given L{Task} will be inserted at the top of the registered stack
329 of L{Task}s (i.e. it will be executed first for the appropriate event).
330
331 The L{focus}, L{tier}, and L{background} parameters specify on which layer
332 the L{Task} will handle events. If focus is True, the L{Task} will be
333 executed in response to an event from a focused control within this
334 L{Tier}. If tier is True, the L{Task} will be executed in response to an
335 event from an unfocused control within this L{Tier}. If background is True,
336 the L{Task} will be executed in response to an event from any control
337 within the tier when the L{Tier} is not active.
338
339 The three layers are mutually exclusive. You may set any combination of
340 focus, tier, and background to True to register the given L{task} on each
341 selected layer in one call. If all three parameters are False, the
342 registration defaults to the focus layer.
343
344 The L{Task} passed to this method must implement the interface defined by
345 L{Task.EventTask}.
346
347 @param task: L{Task} to register
348 @type task: L{Task.Base.Task}
349 @param focus: Should this L{Task} handle events from focused accessibles in
350 this L{Tier}?
351 @type focus: boolean
352 @param tier: Should this L{Task} handle events from unfocused accessibles
353 in this L{Tier}?
354 @type tier: boolean
355 @param background: Should this L{Task} handle events from any accessible in
356 this L{Tier} when the L{Tier} is inactive?
357 @type background: boolean
358 @raise AttributeError: When the L{Task} does not implement
359 L{Task.EventTask}
360 @raise NotImplementedError: When the L{Task} does not properly subclass
361 L{Task.EventTask}
362 '''
363
364 default = not (focus or tier or background)
365 d = {AEConstants.LAYER_FOCUS : focus or default,
366 AEConstants.LAYER_TIER : tier,
367 AEConstants.LAYER_BACKGROUND : background}
368
369 kind = task.getEventType()
370
371 self._initTask(task)
372
373 try:
374 self.tier.setEventInterest(kind, True)
375 except KeyError:
376
377 pass
378 for layer, val in d.items():
379 if val:
380
381
382 curr = self.event_tasks.setdefault((kind, layer), [])
383
384 curr.insert(0, task)
385
387 '''
388 Unregisters a L{Task} from being called on a set interval.
389
390 @param task: L{Task} to unregister
391 @type task: L{Task}
392 @raise KeyError: When the L{Task} is not registered
393 '''
394 tid = id(task)
395 del self.keyed_tasks[tid]
396
397 task.close()
398 task.postClose()
399 self.tier.removeTaskRef(tid)
400
402 '''
403 Unregisters a L{Task} set to execute in response to the given action codes
404 on the given device B{from this L{Perk} only}.
405
406 @param device: Input device to monitor
407 @type device: L{AEInput}
408 @param codes: List of lists of action codes forming the L{AEInput.Gesture}
409 that will trigger the execution of the named L{Task}. For example,
410 codes=[[Keyboard.AEK_CTRL, Keyboard.AEK_TILDE]] indicates the single
411 gesture of simultaneously pressing Ctrl and ~ on the keyboard device.
412 @type codes: list of list of integer
413 @raise KeyError: When a L{AEInput.GestureList} is not registered
414 '''
415
416
417
418 gl = AEInput.GestureList(device, codes)
419 del self.commands[gl]
420 self.tier.removeTaskRef(gl)
421
422 try:
423 device.removeFilter(gl)
424 except (NotImplementedError, AttributeError):
425 pass
426
427
428 try:
429 device.removeKeyCmd(codes)
430 except NotImplementedError:
431 pass
432
434 '''
435 Unregisters a L{Task} so it is no longer executed in response to a change
436 in the given L{AEChooser}.
437
438 @param chooser: Chooser that the L{Task} should no longer observe
439 @type chooser: L{AEChooser}
440 @raise KeyError: When the given L{AEChooser} is not registered
441 '''
442 cid = id(chooser)
443 task = self.keyed_tasks[cid]
444 del self.keyed_tasks[cid]
445
446
447
448
449
450
451 self.tier.removeTaskRef(cid)
452
454 '''
455 Unregisters a named L{Task} B{from this L{Perk} only}. If a Task with the
456 given name is not found in this L{Perk}, an exception is raised.
457
458 @param name: Name of the L{Task} to unregister
459 @type name: string
460 @raise KeyError: When a L{Task} with the given name is not registered
461 '''
462 task = self.named_tasks[name]
463 del self.named_tasks[name]
464
465 task.close()
466 task.postClose()
467 self.tier.removeTaskRef(name)
468
471 '''
472 Unregisters the given L{Task} instance B{from this L{Perk} only}. If the
473 given L{Task} instance was not registered for an event in this L{Perk}, an
474 exception is raised. The L{focus}, L{tier}, and L{background} parameters
475 state from which layer(s) this L{Task} should be unregistered.
476
477 The L{Task} passed to this method must implement the interface defined by
478 L{Task.EventTask}.
479
480 @param task: L{Task} to unregister
481 @type task: L{Task.Base.Task}
482 @raise KeyError: When there are no L{Task}s registered with the type of the
483 given L{Task}
484 @raise ValueError: When the given L{Task} is not registered on one of the
485 specified layers
486 @raise AttributeError: When the L{Task} does not implement
487 L{Task.EventTask}
488 @raise NotImplementedError: When the L{Task} does not properly subclass
489 L{Task.EventTask}
490 @see: L{registerEventTask}
491 '''
492
493 default = not (focus or tier or background)
494 d = {AEConstants.LAYER_FOCUS : focus or default, AEConstants.LAYER_TIER : tier,
495 AEConstants.LAYER_BACKGROUND : background}
496
497 kind = task.getEventType()
498
499 task.close()
500 task.postClose()
501
502
503 try:
504 self.tier.setEventInterest(kind, False)
505 except KeyError:
506
507 pass
508 for layer, val in d.items():
509 if val:
510
511
512 curr = self.event_tasks[(kind, layer)]
513 curr.remove(task)
514
516 '''
517 Gets the L{Task} registered B{in this L{Perk} only} to execute in response
518 to the given L{AEInput.GestureList}.
519
520 Called by L{Tier.Tier.getCommandTask} during a search through all L{Perk}s
521 in the owning L{Tier} for the given key list.
522
523 @param gesture_list: Gestures and device on which they were performed
524 @type gesture_list: L{AEInput.GestureList}
525 @return: Name of the L{Task} set to execute in response to the input
526 gesture or None
527 @rtype: string
528 '''
529 return self.commands.get(gesture_list)
530
532 '''
533 Gets a L{Task} registered under a particular key set to execute in response
534 to events. None is returned if not found.
535
536 Called by L{Tier.Tier.getKeyedTask} during a search through all L{Perk}s
537 in the owning L{Tier} for the given L{AEChooser}.
538
539 @param key: Unique key identifying this L{Task}
540 @type key: integer
541 @return: L{Task} set to execute in response to the chooser or None
542 @rtype: L{Task.ChooserTask.ChooserTask}
543 '''
544 return self.keyed_tasks.get(key)
545
547 '''
548 Gets the L{Task} with the given name if it is registered B{in this L{Perk}
549 only}. If no L{Task} is registered under the given name in this L{Perk},
550 returns None.
551
552 Called by L{Tier.Tier.getNamedTask} during a search through all
553 L{Perk}s in the owning L{Tier} for the given name.
554
555 @param name: Name of the L{Task} to locate
556 @type name: string
557 @return: L{Task} with the given name or None
558 @rtype: L{Task.Base.Task}
559 '''
560 return self.named_tasks.get(name)
561
563 '''
564 Get all registered L{Task}s registered to handle the given L{AEEvent} type
565 B{in this L{Perk} only}. Makes a copy of the list of tasks such that
566 registering or unregistering L{Task}s does not affect the list gotten
567 by this method.
568
569 Called by L{Tier.Tier.getEventTasks} during a search through all
570 L{Perk}s in the owning L{Tier} for the given task type.
571
572 @param event_type: Desired type of L{AEEvent}
573 @type event_type: L{AEEvent} class
574 @param task_layer: Layer on which the desired L{Task}s are registered
575 @type task_layer: integer
576 @return: List of all L{Task}s that handle the given event type on the given
577 layer in this L{Perk}
578 @rtype: list of L{Task.Base.Task}
579 '''
580 return list(self.event_tasks.get((event_type, task_layer), []))
581
583 '''
584 Gets a copy of the segment of the L{Task} chain for target specified by the
585 link type. A copy is returned to avoid iteration errors if the list changes
586 size during execution of the L{Task}s in the chain.
587
588 @param target: Name of the L{Task} to link to
589 @type target: string
590 @param link: One of the CHAIN constants in L{AEConstants.Tools}
591 @type link: integer
592 @return: L{Task} names chained in the given manner
593 @rtype: list of string
594 '''
595 if link == AEConstants.CHAIN_BEFORE:
596 return self.before_chains.get(target, [])[:]
597 elif link == AEConstants.CHAIN_AFTER:
598 return self.after_chains.get(target, [])[:]
599 elif link == AEConstants.CHAIN_AROUND:
600 alist = []
601 if self.around_chains.has_key(target):
602 alist.append(self.around_chains[target])
603 return alist
604
606 '''
607 Adds the before and after segments of the chain for the target specified.
608 The parameters before and after are lists, possibly containing segments
609 from other L{Perk}s. These lists are modified in place for performance.
610 The around segment is returned since it is a single value.
611
612 @param target: Name of the L{Task} to link to
613 @type target: string
614 @return: L{Task} name for around link type, or None if nothing is linked
615 around the target
616 @rtype: string
617 '''
618 try:
619 before.extend(self.before_chains[target])
620 except KeyError:
621 pass
622 try:
623 after.extend(self.after_chains[target])
624 except KeyError:
625 pass
626 return self.around_chains.get(target, None)
627
629 '''
630 Links a L{Task} to the one named in target. The L{Task} will be added
631 either before, after, or around depending on the value of link. Does not
632 allow a L{Task} to link to itself (i.e. name cannot equal target).
633
634 Invokes L{Tier.Tier.addChainRef} to add a reference to this L{Perk} to the
635 L{Tier} which will later have to traverse the chain across L{Perk}s.
636
637 @param name: Name of the L{Task} to link
638 @type name: string
639 @param link: One of the CHAIN constants in L{AEConstants.Tools}
640 @type link: integer
641 @param target: Name of the L{Task} to link to
642 @type target: string
643 @raise ValueError: When the link type is unknown
644 @raise AssertionError: When the name equals the target
645 '''
646 assert name != target
647 if link == AEConstants.CHAIN_BEFORE:
648 self.before_chains.setdefault(target, []).append(name)
649 elif link == AEConstants.CHAIN_AFTER:
650 self.after_chains.setdefault(target, []).append(name)
651 elif link == AEConstants.CHAIN_AROUND:
652 self.around_chains[target] = name
653 else:
654 raise ValueError(link)
655
656 self.tier.addChainRef(target, self)
657
659 '''
660 Unlinks a L{Task} from the one named in target. The L{Task} will be
661 unlinked either before, after, or around depending on the value of link.
662 If link is None, the L{Task} will be unlinked from all cases if
663 possible.
664
665 Invokes L{Tier.Tier.removeChainRef} to delete a reference to this L{Perk}
666 to the L{Tier} which will later have to traverse the chain across L{Perk}s.
667
668 @param name: Name of the L{Task} to link
669 @type name: string
670 @param link: One of the CHAIN constants in L{AEConstants.Tools}
671 @type link: integer
672 @param target: Name of the L{Task} to unlink from
673 @type target: string
674 @raise ValueError: When the named L{Task} is not unlinked at least once or
675 the link type is unknown
676 @raise KeyError: When the target L{Task} does not have a chain segement in
677 this L{Perk} for the given link type
678 '''
679 if link is not None:
680 if link == AEConstants.CHAIN_BEFORE:
681 self.before_chains[target].remove(name)
682 if len(self.before_chains[target]) == 0:
683 del self.before_chains[target]
684 elif link == AEConstants.CHAIN_AFTER:
685 self.after_chains[target].remove(name)
686 if len(self.after_chains[target]) == 0:
687 del self.after_chains[target]
688 elif link == AEConstants.CHAIN_AROUND:
689
690 if self.around_chains[target] == name:
691 del self.around_chains[target]
692 else:
693 raise ValueError(name)
694 else:
695 raise ValueError(link)
696 else:
697
698 count = 0
699
700 try:
701 self.before_chains[target].remove(name)
702 if len(self.before_chains[target]) == 0:
703 del self.before_chains[target]
704 count += 1
705 except (KeyError, ValueError):
706 pass
707 try:
708 self.after_chains[target].remove(name)
709 if len(self.after_chains[target]) == 0:
710 del self.after_chains[target]
711 count += 1
712 except (KeyError, ValueError):
713 pass
714 try:
715 if self.around_chains[target] == name:
716
717 del self.around_chains[target]
718 count += 1
719 except (KeyError, ValueError):
720 pass
721
722 if count == 0:
723
724 raise ValueError
725
726 if not self.around_chains.has_key(target) \
727 and not self.before_chains.has_key(target) \
728 and not self.after_chains.has_key(target) :
729 self.tier.removeChainRef(target, self)
730
732 '''
733 Gets the names of all L{Task}s registered in this L{Perk}.
734
735 @return: All names
736 @rtype: list of string
737 '''
738 return self.named_tasks.keys()
739
741 '''
742 Unregisters a list of input modifiers. Ignores the error cases when the
743 device has no registered modifiers or one of the given modifiers is not
744 registered.
745
746 @param dev: An L{AEInput} device.
747 @type dev: L{AEInput}
748 @param modifiers: A list of key codes used as modifiers.
749 @type modifiers: integer
750 '''
751 for m in modifiers:
752 try:
753 self.registered_modifiers[dev].remove(m)
754 except (KeyError, ValueError):
755 pass
756
758 '''
759 Registers a list of input modifiers.
760
761 @param dev: An L{AEInput} device.
762 @type dev: L{AEInput}
763 @param modifiers: A list of key codes used as modifiers.
764 @type modifiers: integer
765 '''
766 self.perk.registered_modifiers.setdefault(dev, []).extend(modifiers)
767