Index: src/mlx/gui/gui.py
===================================================================
--- src/mlx/gui/gui.py	(revision 35)
+++ src/mlx/gui/gui.py	(revision 36)
@@ -3,4 +3,5 @@
 from statusicon import StatusIcon
 from statusbar import Statusbar
+from update import Updater
 from mlx.gui.common import *
 
@@ -31,8 +32,10 @@
 class GUI(fs.ConnectionListener):
     """The main GUI class."""
-    def __init__(self):
+    def __init__(self, programDirectory, config):
         """Construct the GUI."""
         gobject.threads_init()
 
+        self._programDirectory = programDirectory
+        self._config = config
         self._connecting = False
         self._connected = False
@@ -40,17 +43,18 @@
         self._flight = None
         self._simulator = None
+        self._stdioAfterNewLine = True
 
     def build(self, iconDirectory):
         """Build the GUI."""
         
-        win = gtk.Window()
-        win.set_title("MAVA Logger X " + const.VERSION)
-        win.set_icon_from_file("logo.ico")
-        win.connect("delete-event",
-                    lambda a, b: self.hideMainWindow())
-        win.connect("window-state-event", self._handleMainWindowState)
+        window = gtk.Window()
+        window.set_title("MAVA Logger X " + const.VERSION)
+        window.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
+        window.connect("delete-event",
+                       lambda a, b: self.hideMainWindow())
+        window.connect("window-state-event", self._handleMainWindowState)
 
         mainVBox = gtk.VBox()
-        win.add(mainVBox)
+        window.add(mainVBox)
 
         setupFrame = self._buildSetupFrame()
@@ -71,7 +75,7 @@
         mainVBox.pack_start(self._statusbar, False, False, 0)
 
-        win.show_all()        
-
-        self._mainWindow = win
+        window.show_all()        
+
+        self._mainWindow = window
 
         self._statusIcon = StatusIcon(iconDirectory, self)
@@ -79,5 +83,12 @@
     def run(self):
         """Run the GUI."""
+        if self._config.autoUpdate:
+            self._updater = Updater(self._programDirectory,
+                                    self._config.updateURL,
+                                    self._mainWindow)
+            self._updater.start()
+
         gtk.main()
+
         if self._flight is not None:
             simulator = self._flight.simulator
@@ -173,4 +184,29 @@
         else:
             self.showMainWindow()
+
+    def writeStdIO(self, text):
+        """Write the given text into standard I/O log."""
+        gobject.idle_add(self._writeStdIO, text)
+
+    def _writeStdIO(self, text):
+        """Perform the real writing."""
+        lines = text.splitlines()
+        if text[-1]=="\n":
+            text = ""
+        else:
+            text = lines[-1]
+            lines = lines[:-1]
+            
+        for line in lines:
+            if self._stdioAfterNewLine:
+                line = "[STDIO] " + line
+            self._writeLog(line + "\n")
+            self._stdioAfterNewLine = True
+
+        if text:
+            if self._stdioAfterNewLine:
+                text = "[STDIO] " + text
+            self._writeLog(text)
+            self._stdioAfterNewLine = False
             
     def _connectToggled(self, button):
Index: src/mlx/gui/update.py
===================================================================
--- src/mlx/gui/update.py	(revision 36)
+++ src/mlx/gui/update.py	(revision 36)
@@ -0,0 +1,281 @@
+# The GUI part of the update
+
+#-------------------------------------------------------------------------------
+
+from mlx.gui.common import *
+
+from mlx.update import update, restart
+
+import mlx.const as const
+
+import threading
+import os
+import sys
+
+#-------------------------------------------------------------------------------
+
+class Updater(threading.Thread):
+    """The updater thread."""
+
+    # The removal or renaming of one file equals to the downloading of this
+    # many bytes in time (for the progress calculation)
+    REMOVE2BYTES = 100
+
+    _progressWindow = None
+
+    _progressLabel = None
+    
+    _progressBar = None
+    
+    _progressOKButton = None
+
+    _sudoDialog = None
+
+    @staticmethod
+    def _createGUI(parentWindow):
+        """Create the GUI elements, if needed."""
+        if Updater._progressWindow is not None:
+            return
+
+        Updater._progressWindow = window = gtk.Window()
+        window.set_title("MAVA Logger X " + const.VERSION + " Update")
+        window.set_transient_for(parentWindow)
+        #win.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
+        window.set_size_request(400, -1)
+        window.set_resizable(False)
+        window.set_modal(True)
+        window.connect("delete-event", lambda a, b: True)
+        window.set_deletable(False)
+        window.set_position(gtk.WindowPosition.CENTER_ON_PARENT if pygobject
+                            else gtk.WIN_POS_CENTER_ON_PARENT)
+
+        mainAlignment = gtk.Alignment(xscale = 1.0)
+        mainAlignment.set_padding(padding_top = 4, padding_bottom = 10,
+                                  padding_left = 16, padding_right = 16)
+        window.add(mainAlignment)
+
+        mainVBox = gtk.VBox()
+        mainAlignment.add(mainVBox)
+
+        labelAlignment = gtk.Alignment(xalign = 0.0, xscale = 0.0)
+        Updater._progressLabel = progressLabel = gtk.Label()
+        labelAlignment.add(progressLabel)
+        mainVBox.pack_start(labelAlignment, True, True, 4)
+        
+        Updater._progressBar = progressBar = gtk.ProgressBar()
+        mainVBox.pack_start(progressBar, True, True, 4)
+
+        buttonAlignment = gtk.Alignment(xalign = 0.5, xscale = 0.1)
+        Updater._progressOKButton = progressOKButton = gtk.Button("OK")
+        buttonAlignment.add(progressOKButton)        
+        mainVBox.pack_start(buttonAlignment, True, True, 4)
+
+        Updater._sudoDialog = sudoDialog = \
+            gtk.Dialog("MAVA Logger X " + const.VERSION + " Update",
+                       parentWindow,
+                       gtk.DialogFlags.MODAL if pygobject else gtk.DIALOG_MODAL,
+                       (gtk.STOCK_CANCEL, 0,
+                        gtk.STOCK_OK, 1))
+
+        infoLabelAlignment = gtk.Alignment(xalign = 0.5, xscale = 0.1)
+        infoLabelAlignment.set_padding(padding_top = 4, padding_bottom = 10,
+                                       padding_left = 16, padding_right = 16)
+
+        infoLabel = gtk.Label("There is an update available, but the program cannot write\n"
+                              "its directory due to insufficient privileges.\n\n"
+                              "Click OK, if you want to run a helper program\n"
+                              "with administrator privileges "
+                              "to complete the update,\n"
+                              "Cancel otherwise.")
+        infoLabel.set_justify(gtk.Justification.CENTER if pygobject
+                              else gtk.JUSTIFY_CENTER)
+        infoLabelAlignment.add(infoLabel)
+        sudoDialog.vbox.pack_start(infoLabelAlignment, True, True, 4)
+
+        sudoDialog.set_position(gtk.WindowPosition.CENTER_ON_PARENT if pygobject
+                                else gtk.WIN_POS_CENTER_ON_PARENT)
+
+    def __init__(self, programDirectory, updateURL, parentWindow):
+        """Construct the updater. If not created yet, the windows used by the
+        updater are also created."""
+        super(Updater, self).__init__()
+        
+        self._programDirectory = programDirectory
+        self._updateURL = updateURL
+
+        self._totalProgress = 0
+        self._waitAfterFinish = False
+        self._restart = False
+
+        self._sudoReply = None
+        self._sudoCondition = threading.Condition()
+
+        Updater._createGUI(parentWindow)
+
+    def run(self):
+        """Execute the thread's operation."""
+        gobject.idle_add(self._startUpdate)
+        update(self._programDirectory, self._updateURL, self, fromGUI = True)
+        if not self._waitAfterFinish:
+            gobject.idle_add(self._progressWindow.hide)
+
+    def downloadingManifest(self):
+        """Called when the downloading of the manifest has started."""
+        gobject.idle_add(self._downloadingManifest)
+
+    def _downloadingManifest(self):
+        """Called when the downloading of the manifest has started."""
+        self._progressLabel.set_text("Downloading manifest...")
+        self._progressBar.set_fraction(0)
+
+    def downloadedManifest(self):
+        """Called when the downloading of the manifest has finished."""
+        gobject.idle_add(self._downloadedManifest)
+
+    def _downloadedManifest(self):
+        """Called when the downloading of the manifest has finished."""
+        self._progressLabel.set_text("Downloaded manifest...")
+        self._progressBar.set_fraction(0.05)
+
+    def needSudo(self):
+        """Called when the program is interested in whether we want to run a
+        program with administrator rights to do the update."""
+        gobject.idle_add(self._needSudo)
+        with self._sudoCondition:
+            while self._sudoReply is None:
+                self._sudoCondition.wait(1)
+            return self._sudoReply
+
+    def _needSudo(self):
+        """Called when the program is interested in whether we want to run a
+        program with administrator rights to do the update."""
+        self._sudoDialog.show_all()
+        result = self._sudoDialog.run()
+        self._sudoDialog.hide()
+        with self._sudoCondition:
+            self._sudoReply = result!=0
+            self._sudoCondition.notify()
+        
+    def setTotalSize(self, numToModifyAndNew, totalSize, numToRemove):
+        """Called when the downloading of the files has started."""
+        self._numToModifyAndNew = numToModifyAndNew
+        self._numModifiedOrNew = 0
+        self._totalSize = totalSize
+        self._downloaded = 0
+        self._numToRemove = numToRemove
+        self._numRemoved = 0
+
+        self._totalProgress = self._totalSize + \
+                              (self._numToModifyAndNew + self._numToRemove) * \
+                              Updater.REMOVE2BYTES
+        self._waitAfterFinish = self._totalProgress > 0
+
+    def _startDownload(self):
+        """Called when the download has started."""
+        self._progressLabel.set_text("Downloading files...")
+
+    def setDownloaded(self, downloaded):
+        """Called when a certain number of bytes are downloaded."""
+        self._downloaded = downloaded
+        gobject.idle_add(self._setDownloaded, downloaded)
+
+    def _setDownloaded(self, downloaded):
+        """Called when a certain number of bytes are downloaded."""
+        self._progressLabel.set_text("Downloaded %d of %d" % \
+                                     (downloaded, self._totalSize))
+        self._setProgress()
+
+    def startRenaming(self):
+        """Called when the renaming of files has started."""
+        gobject.idle_add(self._startRenaming)
+
+    def _startRenaming(self):
+        """Called when the renaming of files has started."""
+        self._progressLabel.set_text("Renaming downloaded files...")
+
+    def renamed(self, path, count):
+        """Called when a file has been renamed."""
+        self._numModifiedOrNew = count
+        gobject.idle_add(self._renamed, path, count)
+
+    def _renamed(self, path, count):
+        """Called when a file has been renamed."""
+        self._progressLabel.set_text("Renamed %s" % (path,))
+        self._setProgress()
+
+    def startRemoving(self):
+        """Called when the removing of files has started."""
+        gobject.idle_add(self._startRemoving)
+
+    def _startRemoving(self):
+        """Called when the removing of files has started."""
+        self._progressLabel.set_text("Removing files...")
+
+    def removed(self, path, count):
+        """Called when a file has been removed."""
+        self._numRemoved = count
+        gobject.idle_add(self._removed, path, count)
+
+    def _removed(self, path, count):
+        """Called when a file has been removed."""
+        self._progressLabel.set_text("Removed %s" % (path,))
+        self._setProgress()
+
+    def writingManifest(self):
+        """Called when the writing of the new manifest file has started."""
+        gobject.idle_add(self._writingManifest)
+
+    def _writingManifest(self):
+        """Called when the writing of the new manifest file has started."""
+        self._progressLabel.set_text("Writing the new manifest")
+        
+    def done(self):
+        """Called when the update has been done."""
+        gobject.idle_add(self._done)
+        self._restart = self._totalProgress>0
+
+    def _done(self):
+        """Called when the writing of the new manifest file has started."""
+        self._progressBar.set_fraction(1)
+        if self._totalProgress>0:
+            self._progressLabel.set_text("Finished updating. Press OK to restart the program.")
+            self._progressOKButton.set_sensitive(True)
+        else:
+            self._progressLabel.set_text("There was nothing to update")
+        
+    def _setProgress(self):
+        """Set the progress bar based on the current stage."""
+        if self._totalProgress>0:
+            progress = self._downloaded + \
+                       (self._numModifiedOrNew + self._numRemoved) * \
+                       Updater.REMOVE2BYTES
+            self._progressBar.set_fraction(0.05 + 0.94 * progress / self._totalProgress)
+
+    def failed(self, what):
+        """Called when the downloading has failed."""
+        self._waitAfterFinish = True
+        gobject.idle_add(self._failed, what)
+
+    def _failed(self, what):
+        """Called when the downloading has failed."""        
+        self._progressLabel.set_text("Failed, see the debug log for details.")
+        self._progressBar.set_fraction(1)
+        self._progressOKButton.set_sensitive(True)
+
+    def _startUpdate(self):
+        """Start the update.
+
+        It resets the GUI elements."""
+        self._progressLabel.set_text("")
+        self._progressBar.set_fraction(0)
+        self._progressWindow.show_all()
+        self._progressOKButton.set_sensitive(False)
+        self._progressOKButton.connect("clicked", self._progressOKClicked)
+
+    def _progressOKClicked(self, button):
+        """Called when the OK button on the progress window is clicked."""
+        self._progressWindow.hide()
+        if self._restart:
+            restart()
+
+#-------------------------------------------------------------------------------
