Index: src/orca/liveregions.py
===================================================================
--- src/orca/liveregions.py	(revision 0)
+++ src/orca/liveregions.py	(revision 0)
@@ -0,0 +1,136 @@
+import bisect
+import gobject
+import speech
+import time
+
+
+LIVE_OFF       = -1
+LIVE_NONE      = 0
+LIVE_POLITE    = 1
+LIVE_ASSERTIVE = 2
+LIVE_RUDE      = 3
+
+MSG_KEEPALIVE_TIME = 5  # in seconds
+
+class PriorityQueue:
+    """ This class represents a thread **UNSAFE** priority queue where priority
+    is determined by a given priority.  The entries are also maintained in 
+    chronological order. 
+    
+    TODO: experiment with Queue.Queue to make thread safe
+    """
+    def __init__(self):
+        self.queue = []
+                
+    def enqueue(self, data, priority):
+        """ Add a new element to the queue according to first it's priority and
+        secondly it's timestamp """
+        bisect.insort_left(self.queue, (priority, time.time(), data))
+        
+    def dequeue(self):
+        """get the highest priority element from the queue.  """
+        return self.queue.pop(0)[2]
+    
+    def clear(self):
+        """ Clear the queue """
+        self.queue = []
+        
+    def purgeByKeepAlive(self):
+        """ Purge items from the queue that are older than the keepalive 
+        time """
+        currenttime = time.time()
+        myfilter = lambda item: item[1] + MSG_KEEPALIVE_TIME > currenttime
+        self.queue = filter(myfilter, self.queue)
+        
+    def purgeByPriority(self, priority):
+        """ Purge items from the queue that have a lower than or equal priority
+        than the given argument """
+        myfilter = lambda item: item[0] > priority
+        self.queue = filter(myfilter, self.queue)
+                    
+    def __len__(self):
+        """ Return the length of the queue """
+        return len(self.queue)
+
+
+class LiveRegionManager:
+    def __init__(self, script):
+        self.msg_queue = PriorityQueue()
+        self.channels = ['off', 'default']  
+        self.currentChannel = 0  # TODO grab a user pref setting
+        self._script = script        
+        
+    def handleEvent(self, event):
+        # some debug info
+        # print 'adding', event.source.role, event.source.text.getText(0,-1)
+       
+        if self.channels[self.currentChannel] == 'off':
+            self.msg_queue.clear()
+            return
+        
+        livetype = self._getLiveType(event.source)
+        # Purge our queue of messages based on priority of incoming event and
+        # age of queued message.
+        if livetype == LIVE_NONE:
+            # we won't do anything for bad markup right now
+            return
+        elif livetype == LIVE_POLITE:
+            # nothing to do here now either
+            pass
+        elif livetype ==  LIVE_ASSERTIVE:
+            self.msg_queue.purgeByPriority(LIVE_POLITE)
+        elif livetype == LIVE_RUDE:
+            self.msg_queue.clear()
+            
+        # Add to our queue.  Currently just adding a text message 
+        # TODO: add labeling and description info
+        # output = self._script.expandEOCS(obj)   
+        output = event.source.text.getText(event.detail1,-1)   
+   #     print 'label = ', self._script.getDisplayedLabel(obj)
+        
+        # TODO: might consider adding obj to queue instead of message
+        self.msg_queue.enqueue(output, livetype)
+    
+    def pumpMessages(self):
+        if self.channels[self.currentChannel] != 'off':
+            # If there are messages in the queue and we are not currently speaking
+            # then speak queued message.  May need to consider allowing assertive  
+            #  and/or rude messages through regardless if we are speaking.
+            if len(self.msg_queue) > 0 and not speech.isSpeaking():
+                msg = self.msg_queue.dequeue()
+                speech.speak(msg)
+            
+            # House cleaning on the message queue
+            self.msg_queue.purgeByKeepAlive()
+            
+        # See you again in another second
+        gobject.idle_add(self.pumpMessages)
+        
+    def nextChannel(self):
+        self.currentChannel += 1
+        if self.currentChannel >= len(self.channels):
+            self.currentChannel = 0
+                    
+    def getChannelName(self):
+        return self.channels[self.currentChannel]
+            
+    def _getLiveType(self, obj):
+        attrs = obj.attributes
+        if attrs is None:
+            return LIVE_NONE
+        for attr in attrs:
+            if attr.startswith('live:'):
+                livetype = attr[5:]
+                if livetype == 'polite':
+                    return LIVE_POLITE
+                elif livetype == 'assertive':
+                    return LIVE_ASSERTIVE
+                elif livetype == 'rude':
+                    return LIVE_RUDE
+                else:
+                    return LIVE_NONE
+        return LIVE_NONE
+            
+    def _getAttrDictionary(self, obj):
+        return dict([attr.split(':', 1) for attr in obj.attributes])
+ 
Index: src/orca/Gecko.py
===================================================================
--- src/orca/Gecko.py	(revision 2688)
+++ src/orca/Gecko.py	(working copy)
@@ -39,8 +39,10 @@
 import braillegenerator
 import debug
 import default
+import gobject
 import input_event
 import keybindings
+import liveregions as live
 import mag
 import orca
 import orca_state
@@ -1629,6 +1631,10 @@
         #
         self.madeFindAnnouncement = False
         
+        # Create the live region manager and start the message manager
+        self.liveMngr = live.LiveRegionManager(self)
+        gobject.idle_add(self.liveMngr.pumpMessages)
+        
     def getWhereAmI(self):
         """Returns the "where am I" class for this script.
         """
@@ -2037,6 +2043,12 @@
                 # interrupted.
                 #
                 _("Speaks entire document."))
+        self.inputEventHandlers["liveChannelChanger"] = \
+            input_event.InputEventHandler(
+                Script.liveChannelChanger,
+                # Translators: 
+                #
+                _("Changes the live region channel."))
                 
         
 
@@ -2053,6 +2065,8 @@
             self.onDocumentLoadStopped
         listeners["object:visible-data-changed"]            = \
             self.onVisibleDataChanged
+        listeners["object:children-changed"]                = \
+            self.onChildrenChanged
 
         # [[[TODO: HACK - WDW we need to accomodate Gecko's incorrect
         # use of underscores instead of dashes until they fix their bug.
@@ -2060,7 +2074,7 @@
         #
         listeners["object:property-change:accessible_value"] = \
             self.onValueChanged
-
+        
         return listeners
 
     def __getArrowBindings(self):
@@ -2499,6 +2513,15 @@
                 1 << settings.MODIFIER_ORCA,
                 1 << settings.MODIFIER_ORCA,
                 self.inputEventHandlers["toggleStructuralNavigationHandler"]))
+                
+        keyBindings.add(
+            keybindings.KeyBinding(
+                "y",
+                (1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT \
+                 | 1 << atspi.Accessibility.MODIFIER_CONTROL),
+                0,
+                self.inputEventHandlers["liveChannelChanger"]))
 
         if controlCaretNavigation:
             for keyBinding in self.__getArrowBindings().keyBindings:
@@ -3273,6 +3296,9 @@
         # If text is deleted from an object, we want to trash our
         # cache of the unicode text.
         #
+        
+    #    print 'onTextDeleted', event.type, event.source.text.getText(0,-1), event.source.attributes
+        
         try:
             del event.source.unicodeText
         except:
@@ -3309,12 +3335,25 @@
         # If text is inserted into an object, we want to trash our
         # cache of the unicode text.
         #
+        print 'onTextInserted', event.source.role, event.detail1, event.detail2
+        if self.isLiveRegion(event.source):
+            self.liveMngr.handleEvent(event)
+        
         try:
             del event.source.unicodeText
         except:
             pass
 
         default.Script.onTextInserted(self, event)
+        
+    def isLiveRegion(self, obj):
+        attrs = obj.attributes
+        if attrs is None:
+            return False
+        for attr in attrs:
+            if attr.startswith('live:'):
+                return True
+        return False
 
     def onDocumentReload(self, event):
         """Called when the reload button is hit for a web page."""
@@ -3664,6 +3703,10 @@
             self.updateBraille(obj)
             self.speakContents(
                 self.getLineContentsAtOffset(obj, characterOffset))
+                
+    def onChildrenChanged(self, event):
+        #print 'onChildrenChanged', event.source.role, event.source.attributes
+        pass
 
     def handleProgressBarUpdate(self, event, obj):
         """Determine whether this progress bar event should be spoken or not.
@@ -4437,6 +4480,14 @@
             if attr.startswith('xml-roles'):
                 return True
         return False
+        
+    def liveChannelChanger(self, keyboardEvent):
+        self.liveMngr.nextChannel()
+        # Translators: This announces the live region channel that is being 
+        # switched to.
+        #
+        speech.speak(_('live region channel %s') \
+                                          %self.liveMngr.getChannelName())
 
     def getCharacterOffsetInParent(self, obj):
         """Returns the character offset of the embedded object
Index: src/orca/Makefile.am
===================================================================
--- src/orca/Makefile.am	(revision 2688)
+++ src/orca/Makefile.am	(working copy)
@@ -32,6 +32,7 @@
 	J2SE-access-bridge.py \
 	keybindings.py \
 	keynames.py \
+        liveregions.py \
 	mag.py \
 	orca.py \
 	orca_console_prefs.py \
