Index: src/mlx/fsuipc.py
===================================================================
--- src/mlx/fsuipc.py	(revision 162)
+++ src/mlx/fsuipc.py	(revision 168)
@@ -308,5 +308,5 @@
             self._waitConnectionRequest()
             
-            if self._connect():
+            if self._connect()>0:
                 self._handleConnection()
 
@@ -319,5 +319,5 @@
                 self._requestCondition.wait()
             
-    def _connect(self, autoReconnection = False):
+    def _connect(self, autoReconnection = False, attempts = 0):
         """Try to connect to the flight simulator via FSUIPC
 
@@ -325,6 +325,15 @@
         not due to no longer requested.
         """
-        attempts = 0
         while self._connectionRequested:
+            if attempts>=self.NUM_CONNECTATTEMPTS:
+                self._connectionRequested = False
+                if autoReconnection:
+                    Handler._callSafe(lambda:     
+                                      self._connectionListener.disconnected())
+                else:
+                    Handler._callSafe(lambda:     
+                                      self._connectionListener.connectionFailed())
+                return 0
+                
             try:
                 attempts += 1
@@ -338,19 +347,10 @@
                                                                          description))
                 self._connected = True
-                return True
+                return attempts
             except Exception, e:
-                print "fsuipc.Handler._connect: connection failed: " + str(e)
+                print "fsuipc.Handler._connect: connection failed: " + str(e) + \
+                      " (attempts: %d)" % (attempts,)
                 if attempts<self.NUM_CONNECTATTEMPTS:
                     time.sleep(self.CONNECT_INTERVAL)
-                else:
-                    self._connectionRequested = False
-                    if autoReconnection:
-                        Handler._callSafe(lambda:     
-                                          self._connectionListener.disconnected())
-                    else:
-                        Handler._callSafe(lambda:     
-                                          self._connectionListener.connectionFailed())
-
-        return False
                 
     def _handleConnection(self):
@@ -386,5 +386,5 @@
             self._connected = False
             
-    def _processRequest(self, request, time):
+    def _processRequest(self, request, time, attempts):
         """Process the given request. 
 
@@ -409,5 +409,5 @@
             except Exception as e:
                 print "fsuipc.Handler._processRequest: FSUIPC connection failed (" + \
-                      str(e) + "), reconnecting."
+                      str(e) + "), reconnecting (attempts=%d)." % (attempts,)
                 needReconnect = True
 
@@ -416,5 +416,7 @@
                     self._requests.insert(0, request)
                 self._disconnect()
-                self._connect(autoReconnection = True)
+                return self._connect(autoReconnection = True, attempts = attempts)
+            else:
+                return 0
         finally:
             self._requestCondition.acquire()
@@ -424,4 +426,5 @@
 
         Will be called with the request lock held."""
+        attempts = 0
         while self._connectionRequested and self._periodicRequests:
             self._periodicRequests.sort()
@@ -433,5 +436,5 @@
                 break
             
-            self._processRequest(request, t)
+            attempts = self._processRequest(request, t, attempts)
 
         while self._connectionRequested and self._requests:
@@ -439,5 +442,5 @@
             del self._requests[0]
 
-            self._processRequest(request, None)
+            attempts = self._processRequest(request, None, attempts)
 
         return self._connectionRequested
@@ -490,4 +493,19 @@
 
         return timestamp
+
+    @staticmethod
+    def _appendHotkeyData(data, offset, hotkey):
+        """Append the data for the given hotkey to the given array, that is
+        intended to be passed to requestWrite call on the handler."""
+        data.append((offset + 0, "b", ord(hotkey.key)))
+
+        modifiers = 0
+        if hotkey.ctrl: modifiers |= 0x02
+        if hotkey.shift: modifiers |= 0x01
+        data.append((offset + 1, "b", modifiers))
+
+        data.append((offset + 2, "b", 0))
+        
+        data.append((offset + 3, "b", 0))        
 
     def __init__(self, connectionListener, connectAttempts = -1,
@@ -515,7 +533,8 @@
         self._aircraft = None
 
-        self._handler = Handler(connectionListener,
+        self._handler = Handler(self,
                                 connectAttempts = connectAttempts,
                                 connectInterval = connectInterval)
+        self._connectionListener = connectionListener
         self._handler.start()
 
@@ -537,4 +556,12 @@
         self._flareStart = None
         self._flareStartFS = None
+
+        self._hotkeyLock = threading.Lock()
+        self._hotkeys = None
+        self._hotkeySetID = 0
+        self._hotkeySetGeneration = 0
+        self._hotkeyOffets = None
+        self._hotkeyRequestID = None
+        self._hotkeyCallback = None
 
         self._latin1decoder = codecs.getdecoder("iso-8859-1")
@@ -665,4 +692,43 @@
         self._syncTime = False
         self._nextSyncTime = -1
+
+    def listenHotkeys(self, hotkeys, callback):
+        """Start listening to the given hotkeys.
+
+        callback is function expecting two arguments:
+        - the ID of the hotkey set as returned by this function,
+        - the list of the indexes of the hotkeys that were pressed."""
+        with self._hotkeyLock:
+            assert self._hotkeys is None
+
+            self._hotkeys = hotkeys
+            self._hotkeySetID += 1
+            self._hotkeySetGeneration = 0
+            self._hotkeyCallback = callback
+            
+            self._handler.requestRead([(0x320c, "u")],
+                                      self._handleNumHotkeys,
+                                      (self._hotkeySetID,
+                                       self._hotkeySetGeneration))
+
+            return self._hotkeySetID
+
+    def clearHotkeys(self):
+        """Clear the current hotkey set.
+
+        Note that it is possible, that the callback function set either
+        previously or after calling this function by listenHotkeys() will be
+        called with data from the previous hotkey set.
+
+        Therefore it is recommended to store the hotkey set ID somewhere and
+        check that in the callback function. Right before calling
+        clearHotkeys(), this stored ID should be cleared so that the check
+        fails for sure."""
+        with self._hotkeyLock:
+            if self._hotkeys is not None:
+                self._hotkeys = None
+                self._hotkeySetID += 1
+                self._hotkeyCallback = None
+                self._clearHotkeyRequest()
             
     def disconnect(self, closingMessage = None, duration = 3):
@@ -673,4 +739,5 @@
         
         self._stopNormal()
+        self.clearHotkeys()
         if closingMessage is None:
             self._handler.disconnect()
@@ -678,4 +745,29 @@
             self.sendMessage(closingMessage, duration = duration,
                              _disconnect = True)
+
+    def connected(self, fsType, descriptor):
+        """Called when a connection has been established to the flight
+        simulator of the given type."""
+        with self._hotkeyLock:
+            if self._hotkeys is not None:
+                self._hotkeySetGeneration += 1
+            
+                self._handler.requestRead([(0x320c, "u")],
+                                          self._handleNumHotkeys,
+                                          (self._hotkeySetID,
+                                           self._hotkeySetGeneration))
+        self._connectionListener.connected(fsType, descriptor)
+
+    def connectionFailed(self):
+        """Called when the connection could not be established."""
+        with self._hotkeyLock:
+            self._clearHotkeyRequest()
+        self._connectionListener.connectionFailed()
+
+    def disconnected(self):
+        """Called when a connection to the flight simulator has been broken."""
+        with self._hotkeyLock:
+            self._clearHotkeyRequest()
+        self._connectionListener.disconnected()
 
     def _startDefaultNormal(self):
@@ -915,4 +1007,107 @@
         """Callback for a fuel setting request."""
         pass
+
+    def _handleNumHotkeys(self, data, (id, generation)):
+        """Handle the result of the query of the number of hotkeys"""
+        with self._hotkeyLock:
+            if id==self._hotkeySetID and generation==self._hotkeySetGeneration:
+                numHotkeys = data[0]
+                print "fsuipc.Simulator._handleNumHotkeys: numHotkeys:", numHotkeys
+                data = [(0x3210 + i*4, "d") for i in range(0, numHotkeys)]        
+                self._handler.requestRead(data, self._handleHotkeyTable,
+                                          (id, generation))
+
+    def _setupHotkeys(self, data):
+        """Setup the hiven hotkeys and return the data to be written.
+
+        If there were hotkeys set previously, they are reused as much as
+        possible. Any of them not reused will be cleared."""
+        hotkeys = self._hotkeys
+        numHotkeys = len(hotkeys)
+
+        oldHotkeyOffsets = set([] if self._hotkeyOffets is None else
+                               self._hotkeyOffets)
+
+        self._hotkeyOffets = []
+        numOffsets = 0
+
+        while oldHotkeyOffsets:
+            offset = oldHotkeyOffsets.pop()
+            self._hotkeyOffets.append(offset)
+            numOffsets += 1
+
+            if numOffsets>=numHotkeys:
+                break
+
+        for i in range(0, len(data)):
+            if numOffsets>=numHotkeys:
+                break
+
+            if data[i]==0:
+                self._hotkeyOffets.append(0x3210 + i*4)
+                numOffsets += 1
+
+        writeData = []
+        for i in range(0, numOffsets):
+            Simulator._appendHotkeyData(writeData,
+                                        self._hotkeyOffets[i],
+                                        hotkeys[i])
+
+        for offset in oldHotkeyOffsets:
+            writeData.append((offset, "u", long(0)))
+
+        return writeData
+
+    def _handleHotkeyTable(self, data, (id, generation)):
+        """Handle the result of the query of the hotkey table."""
+        with self._hotkeyLock:
+            if id==self._hotkeySetID and generation==self._hotkeySetGeneration:
+                writeData = self._setupHotkeys(data)
+                self._handler.requestWrite(writeData,
+                                           self._handleHotkeysWritten,
+                                           (id, generation))
+            
+    def _handleHotkeysWritten(self, success, (id, generation)):
+        """Handle the result of the hotkeys having been written."""
+        with self._hotkeyLock:            
+            if success and id==self._hotkeySetID and \
+            generation==self._hotkeySetGeneration:
+                data = [(offset + 3, "b") for offset in self._hotkeyOffets]
+        
+                self._hotkeyRequestID = \
+                    self._handler.requestPeriodicRead(0.5, data,
+                                                      self._handleHotkeys,
+                                                      (id, generation))
+
+    def _handleHotkeys(self, data, (id, generation)):
+        """Handle the hotkeys."""        
+        with self._hotkeyLock:
+            if id!=self._hotkeySetID or generation!=self._hotkeySetGeneration:
+                return
+
+            callback = self._hotkeyCallback
+            offsets = self._hotkeyOffets
+
+        hotkeysPressed = []
+        for i in range(0, len(data)):
+            if data[i]!=0:
+                hotkeysPressed.append(i)
+
+        if hotkeysPressed:
+            data = []
+            for index in hotkeysPressed:
+                data.append((offsets[index]+3, "b", int(0)))
+            self._handler.requestWrite(data, self._handleHotkeysCleared)
+
+            callback(id, hotkeysPressed)
+
+    def _handleHotkeysCleared(self, sucess, extra):
+        """Callback for the hotkey-clearing write request."""        
+
+    def _clearHotkeyRequest(self):
+        """Clear the hotkey request in the handler if there is any."""
+        if self._hotkeyRequestID is not None:
+            self._handler.clearPeriodic(self._hotkeyRequestID)
+            self._hotkeyRequestID = None
                                                   
 #------------------------------------------------------------------------------
