Index: src/mlx/config.py
===================================================================
--- src/mlx/config.py	(revision 132)
+++ src/mlx/config.py	(revision 133)
@@ -232,6 +232,12 @@
             value = config.get(Config._messageTypesSection, option)
             return const.string2messageLevel(value)
+        elif messageType in [const.MESSAGETYPE_LOGGER_ERROR,
+                             const.MESSAGETYPE_FAULT,
+                             const.MESSAGETYPE_NOGO,
+                             const.MESSAGETYPE_GATE_SYSTEM,
+                             const.MESSAGETYPE_HELP]:            
+            return const.MESSAGELEVEL_BOTH
         else:
-            return const.MESSAGELEVEL_NONE
+            return const.MESSAGELEVEL_FS
 
     def _getMessageTypeLevelOptionName(self, messageType):
Index: src/mlx/const.py
===================================================================
--- src/mlx/const.py	(revision 132)
+++ src/mlx/const.py	(revision 133)
@@ -198,7 +198,9 @@
 
 # Message type: logger error
+# FIXME: cannot set the hotkey
 MESSAGETYPE_LOGGER_ERROR = 1
 
 # Message type: information
+# FIXME: flare time begin (3 sec)
 MESSAGETYPE_INFORMATION = 2
 
@@ -210,13 +212,17 @@
 
 # Message type: gate system messages
+# FIXME: the available gates when arriving to LHBP (10 sec)
 MESSAGETYPE_GATE_SYSTEM = 5
 
 # Message type: environment messages
+# FIXME: flight plan closed (5 sec)
 MESSAGETYPE_ENVIRONMENT = 6
 
 # Message type: help messages
+# FIXME: don't forget V speeds when stage is PUSHANDTAXI (5 sec)
 MESSAGETYPE_HELP = 7
 
 # Message type: visibility messages
+# FIXME: the visibility, once when below 2000 RA, then when below 1000 RA (5 sec)
 MESSAGETYPE_VISIBILITY = 8
 
@@ -290,4 +296,9 @@
 #-------------------------------------------------------------------------------
 
+# Sound: ding
+SOUND_DING = "ding.wav"
+
+#-------------------------------------------------------------------------------
+
 # The available gates at LHBP
 lhbpGateNumbers = []
@@ -316,2 +327,4 @@
 languages = ["$system", "en_GB", "hu_HU"]
 
+#-------------------------------------------------------------------------------
+
Index: src/mlx/fs.py
===================================================================
--- src/mlx/fs.py	(revision 89)
+++ src/mlx/fs.py	(revision 133)
@@ -4,6 +4,9 @@
 
 import const
+from sound import startSound
 
 import fsuipc
+import threading
+import time
 
 #-------------------------------------------------------------------------------
@@ -41,4 +44,95 @@
            "Only MS Flight Simulator 2004 and X are supported"
     return fsuipc.Simulator(connectionListener, connectAttempts = 3)
+
+#-------------------------------------------------------------------------------
+
+class MessageThread(threading.Thread):
+    """Thread to handle messages."""
+    def __init__(self, config, simulator):
+        """Initialize the message thread with the given configuration and
+        simulator."""
+        super(MessageThread, self).__init__()
+
+        self._config = config
+        self._simulator = simulator
+
+        self._requestCondition = threading.Condition()
+        self._messages = []
+        self._nextMessageTime = None
+        self._toQuit = False
+
+        self.daemon = True
+
+    def add(self, messageType, text, duration):
+        """Add the given message to the requested messages."""
+        with self._requestCondition:
+            self._messages.append((messageType, text, duration))
+            self._requestCondition.notify()
+
+    def quit(self):
+        """Quit the thread."""
+        with self._requestCondition:
+            self._toQuit = True
+            self._requestCondition.notifty()
+        self.join()
+
+    def run(self):
+        """Perform the thread's operation."""
+        while True:
+            (messageType, text, duration) = (None, None, None)
+            with self._requestCondition:
+                now = time.time()
+                while not self._toQuit and \
+                      ((self._nextMessageTime is not None and \
+                        self._nextMessageTime>now) or \
+                       not self._messages):
+                    self._requestCondition.wait(1)
+                    now = time.time()
+
+                if self._toQuit: return
+                if self._nextMessageTime is None or \
+                   self._nextMessageTime<=now:
+                    self._nextMessageTime = None
+
+                    if self._messages:
+                        (messageType, text, duration) = self._messages[0]
+                        del self._messages[0]
+
+            if text is not None:        
+                self._sendMessage(messageType, text, duration)
+
+    def _sendMessage(self, messageType, text, duration):
+        """Send the message and setup the next message time."""
+        messageLevel = self._config.getMessageTypeLevel(messageType)
+        if messageLevel==const.MESSAGELEVEL_SOUND or \
+           messageLevel==const.MESSAGELEVEL_BOTH:
+            startSound(const.SOUND_DING)
+        if (messageLevel==const.MESSAGELEVEL_FS or \
+            messageLevel==const.MESSAGELEVEL_BOTH):
+            self._simulator.sendMessage("[MLX] " + text, duration = duration)
+        self._nextMessageTime = time.time() + duration
+
+#-------------------------------------------------------------------------------
+
+_messageThread = None
+
+#-------------------------------------------------------------------------------
+
+def setupMessageSending(config, simulator):
+    """Setup message sending with the given config and simulator."""
+    global _messageThread
+    if _messageThread is not None:
+        _messageThread.quit()
+    _messageThread = MessageThread(config, simulator)
+    _messageThread.start()
+
+#-------------------------------------------------------------------------------
+
+def sendMessage(messageType, text, duration = 3):
+    """Send the given message of the given type into the simulator and/or play
+    a corresponding sound."""
+    global _messageThread
+    if _messageThread is not None:
+        _messageThread.add(messageType, text, duration)
 
 #-------------------------------------------------------------------------------
Index: src/mlx/fsuipc.py
===================================================================
--- src/mlx/fsuipc.py	(revision 117)
+++ src/mlx/fsuipc.py	(revision 133)
@@ -436,5 +436,6 @@
     normalData = timeData + \
                  [ (0x3d00, -256),           # The name of the current aircraft
-                   (0x3c00, -256) ]          # The path of the current AIR file
+                   (0x3c00, -256),           # The path of the current AIR file
+                   (0x1274, "h") ]           # Text display mode
 
     flareData1 = [ (0x023a, "b"),            # Seconds of time
@@ -496,4 +497,6 @@
         self._handler.start()
 
+        self._scroll = False
+
         self._normalRequestID = None
 
@@ -576,4 +579,19 @@
             self._flareRequestID = None
 
+    def sendMessage(self, message, duration = 3):
+        """Send a message to the pilot via the simulator.
+
+        duration is the number of seconds to keep the message displayed."""
+        
+        if self._scroll:
+            if duration==0: duration = -1
+            elif duration == 1: duration = -2
+            else: duration = -duration
+
+        data = [(0x3380, -1 - len(message), message),
+                (0x32fa, 'h', duration)]
+
+        self._handler.requestWrite(data, self._handleMessageSent)
+            
     def disconnect(self):
         """Disconnect from the simulator."""
@@ -613,4 +631,6 @@
 
         createdNewModel = self._setAircraftName(timestamp, data[5], data[6])
+
+        self._scroll = data[7]!=0
         
         if self._monitoringRequested and not self._monitoring:
@@ -763,4 +783,8 @@
         dow = zfw - payload
         callback(dow, payload, zfw, grossWeight)
+
+    def _handleMessageSent(self, success, extra):
+        """Callback for a message sending request."""
+        pass
                                                   
 #------------------------------------------------------------------------------
Index: src/mlx/gui/gui.py
===================================================================
--- src/mlx/gui/gui.py	(revision 131)
+++ src/mlx/gui/gui.py	(revision 133)
@@ -298,4 +298,6 @@
         self._connected = True
         self._logger.untimedMessage("Connected to the simulator %s" % (descriptor,))
+        fs.sendMessage(const.MESSAGETYPE_INFORMATION,
+                       "Welcome to MAVA Logger X " + const.VERSION)
         gobject.idle_add(self._handleConnected, fsType, descriptor)
 
@@ -680,4 +682,5 @@
         if self._simulator is None:
             self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
+            fs.setupMessageSending(self.config, self._simulator)
 
         self._flight.simulator = self._simulator
Index: src/mlx/logger.py
===================================================================
--- src/mlx/logger.py	(revision 97)
+++ src/mlx/logger.py	(revision 133)
@@ -3,4 +3,5 @@
 #--------------------------------------------------------------------------------------
 
+from fs import sendMessage
 import const
 import util
@@ -74,4 +75,5 @@
         if stage==const.STAGE_END:
             self.untimedMessage("Rating: %.0f" % (self.getRating(),))
+        sendMessage(const.MESSAGETYPE_INFORMATION, "Flight stage: " + s, 3)
         
     def fault(self, faultID, timestamp, what, score):
@@ -87,8 +89,13 @@
         self._faults[faultID] = score
         if score==Logger.NO_GO_SCORE:
-            lineIndex = self.message(timestamp, "%s (NO GO)" % (what))
+            text = "%s (NO GO)" % (what)
         else:
-            lineIndex = self.message(timestamp, "%s (%.1f)" % (what, score))
+            text = "%s (%.1f)" % (what, score)
+        lineIndex = self.message(timestamp, "%s (NO GO)" % (what))
         self._faultLineIndexes.append(lineIndex)
+        (messageType, duration) = (const.MESSAGETYPE_NOGO, 10) \
+                                  if score==Logger.NO_GO_SCORE \
+                                  else (const.MESSAGETYPE_FAULT, 5)
+        sendMessage(messageType, text, duration)            
 
     def noGo(self, faultID, timestamp, what):
Index: src/mlx/mlx.py
===================================================================
--- src/mlx/mlx.py	(revision 123)
+++ src/mlx/mlx.py	(revision 133)
@@ -3,4 +3,5 @@
 from config import Config
 from i18n import setLanguage
+from sound import initializeSound
 
 import os
@@ -39,4 +40,6 @@
     sys.stderr = StdIOHandler(gui)
 
+    initializeSound(os.path.join(programDirectory, "sounds"))
+
     try:
         gui.build(programDirectory)
Index: src/mlx/pyuipc_sim.py
===================================================================
--- src/mlx/pyuipc_sim.py	(revision 117)
+++ src/mlx/pyuipc_sim.py	(revision 133)
@@ -247,4 +247,8 @@
         self.payload = []
         for i in range(0, 61): self.payload.append(0.0)
+
+        self.textScrolling = False
+        self.message = ""
+        self.messageDuration = 0
 
     def read(self, offset):
@@ -406,4 +410,6 @@
         elif offset==0x1260:       # External 2 tank capacity
             return self._getFuelCapacity(self.FUEL_EXTERNAL_2)
+        elif offset==0x1274:       # Text display mode
+            return 1 if self.textScrolling else 0
         elif offset==0x13fc:       # The number of the payload stations
             return self.payloadCount
@@ -426,4 +432,8 @@
                 if self.radioAltitude is None else self.radioAltitude
             return (radioAltitude * const.FEETTOMETRES * 65536.0)
+        elif offset==0x32fa:       # Message duration
+            return self.messageDuration
+        elif offset==0x3380:       # Message
+            return self.message
         elif offset==0x3364:       # Frozen
             return 1 if self.frozen else 0
@@ -590,4 +600,6 @@
         elif offset==0x1260:       # External 2 tank capacity
             self._setFuelCapacity(self.FUEL_EXTERNAL_2, value)
+        elif offset==0x1274:       # Text display mode
+            textScrolling = value!=0
         elif offset==0x13fc:       # The number of the payload stations
             self.payloadCount = int(value)
@@ -605,4 +617,8 @@
         elif offset==0x31e4:       # Radio altitude
             raise FSUIPCException(ERR_DATA)
+        elif offset==0x32fa:       # Message duration
+            self.messageDuration = value
+        elif offset==0x3380:       # Message
+            self.message = value
         elif offset==0x3364:       # Frozen
             self.frozen = value!=0
@@ -1137,5 +1153,13 @@
                                                        lambda word:
                                                        float(word)*const.KGSTOLB)
+        self._valueHandlers["textScrolling"] = (0x1274, "h",
+                                                CLI.bool2str, CLI.str2bool)
                                                             
+        self._valueHandlers["messageDuration"] = (0x32fa, "h",
+                                                  lambda value: value,
+                                                  lambda word: int(word))
+        self._valueHandlers["message"] = (0x3380, -128,
+                                          lambda value: value,
+                                          lambda word: word)
     def default(self, line):
         """Handle unhandle commands."""
Index: src/mlx/sound.py
===================================================================
--- src/mlx/sound.py	(revision 133)
+++ src/mlx/sound.py	(revision 133)
@@ -0,0 +1,169 @@
+# Module to handle sound playback
+
+#------------------------------------------------------------------------------
+
+import os
+
+#------------------------------------------------------------------------------
+
+if os.name=="nt":
+    import time
+    import threading
+    from ctypes import windll, c_buffer
+
+    class MCIException(Exception):
+        """MCI exception."""
+        def __init__(self, mci, command, errorCode):
+            """Construct an MCI exception for the given error code."""
+            message = "MCI error: %s: %s" % (command, mci.getErrorString(errorCode))
+            super(MCIException, self).__init__(message)
+
+    class MCI:
+        """Interface for the Media Control Interface."""
+        def __init__(self):
+            """Construct the interface."""
+            self.w32mci = windll.winmm.mciSendStringA
+            self.w32mcierror = windll.winmm.mciGetErrorStringA
+
+        def send(self, command):
+            """Send the given command to the MCI."""
+            buffer = c_buffer(255)
+            errorCode = self.w32mci(str(command), buffer, 254, 0)
+            if errorCode:
+                raise MCIException(self, command, errorCode)
+            else:
+                return buffer.value
+
+        def getErrorString(self, errorCode):
+            """Get the string representation of the given error code."""
+            buffer = c_buffer(255)
+            self.w32mcierror(int(errorCode), buffer, 254)
+            return buffer.value
+
+    class SoundThread(threading.Thread):
+        """The thread controlling the playback of sounds."""
+        def __init__(self, soundsDirectory):
+            threading.Thread.__init__(self)
+
+            self._soundsDirectory = soundsDirectory
+            self._mci = MCI()
+
+            self._requestCondition = threading.Condition()
+            self._requestedPaths = []
+            self._pendingAliases = []
+            self._count = 0
+
+            self.daemon = True
+
+        def requestSound(self, name):
+            """Request the playback of the sound with the given name."""
+            path = os.path.join(self._soundsDirectory, name)
+            with self._requestCondition:
+                self._requestedPaths.append(path)
+                self._requestCondition.notify()
+
+        def run(self):
+            """Perform the operation of the thread.
+
+            It waits for a request or a timeout. If a request is received, that
+            is started to be played back. If a timeout occurs, the file is
+            closed."""
+
+            while True:
+                with self._requestCondition:
+                    if not self._requestedPaths:
+                        if self._pendingAliases:
+                            timeout = max(time.time() -
+                                          self._pendingAliases[0][0], 0.0)
+                        else:
+                            timeout = 10.0
+                            
+                        self._requestCondition.wait(timeout)
+
+                    requestedPaths = []
+                    for path in self._requestedPaths:
+                        requestedPaths.append((path, self._count))
+                        self._count += 1
+                    self._requestedPaths = []
+
+                    now = time.time()
+                    aliasesToClose = []
+                    while self._pendingAliases and \
+                          self._pendingAliases[0][0]<=now:
+                        aliasesToClose.append(self._pendingAliases[0][1])
+                        del self._pendingAliases[0]
+
+                for alias in aliasesToClose:
+                    try:
+                        print "Closing", alias
+                        self._mci.send("close " + alias)
+                        print "Closed", alias
+                    except Exception, e:
+                        print "Failed closing " + alias + ":", str(e)
+
+                for (path, counter) in requestedPaths:
+                    try:
+                        alias = "mlxsound%d" % (counter,)
+                        print "Starting to play", path, "as", alias
+                        self._mci.send("open \"%s\" alias %s" % \
+                                       (path, alias))
+                        self._mci.send("set %s time format milliseconds" % \
+                                       (alias,))
+                        lengthBuffer = self._mci.send("status %s length" % \
+                                                      (alias,))
+                        self._mci.send("play %s from 0 to %s" % \
+                                       (alias, lengthBuffer))
+                        length = int(lengthBuffer)
+                        timeout = time.time() + length / 1000.0
+                        with self._requestCondition:
+                            self._pendingAliases.append((timeout, alias))
+                            self._pendingAliases.sort()
+                        print "Started to play", path
+                    except Exception, e:
+                        print "Failed to start playing " + path + ":", str(e)
+
+    _thread = None
+
+    def initializeSound(soundsDirectory):
+        """Initialize the sound handling with the given directory containing
+        the sound files."""
+        global _thread
+        _thread = SoundThread(soundsDirectory)
+        _thread.start()
+
+    def startSound(name):
+        """Start playing back the given sound.
+        
+        name should be the name of a sound file relative to the sound directory
+        given in initializeSound."""
+        _thread.requestSound(name)
+        
+#------------------------------------------------------------------------------
+
+else: # os.name!="nt"
+    def initializeSound(soundsDirectory):
+        """Initialize the sound handling with the given directory containing
+        the sound files."""
+        pass
+
+    def startSound(name):
+        """Start playing back the given sound.
+
+        FIXME: it does not do anything currently, but it should."""
+        pass
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+if __name__ == "__main__":
+    initializeSound("e:\\home\\vi\\tmp")
+    startSound("malev.mp3")
+    time.sleep(5)
+    startSound("ding.wav")
+    time.sleep(5)
+    startSound("ding.wav")
+    time.sleep(5)
+    startSound("ding.wav")
+    time.sleep(50)
+
+#------------------------------------------------------------------------------
