Index: MLXMANIFEST
===================================================================
--- MLXMANIFEST	(revision 510)
+++ MLXMANIFEST	(revision 513)
@@ -7,5 +7,5 @@
 library/fnmatch.pyc	3414	4d50000f78e903598af06ef1c5cfb3ef
 library/libexpat-1.dll	143096	701659d3689bf4a8265b669b7d774255
-library/gtk/_gtk.pyc	549	375cff8defe663bc7e8a404bafdbf319
+library/gtk/_gtk.pyc	549	0d0a77cca8e542a13b7f8ed84ae3a547
 library/encodings/cp858.pyc	7591	02054a0b8e91b9758826eb70cddf096f
 library/mlx/pirep.pyc	4623	2a008f269c900fb604b94b4e427f61b5
@@ -13,5 +13,5 @@
 library/encodings/unicode_internal.pyc	2022	1e902113491e0c334b44deb0c8940e01
 library/libpango-1.0-0.dll	333729	5a14efd93ee27cfe960220fe4a904911
-library/mlx/gui/gui.pyc	50869	73790abccc30d5cbea66bd3801346221
+library/mlx/gui/gui.pyc	51077	c54af5d2051a31bc3c46a7d690cd1552
 library/stat.pyc	2503	d3b6dc7a54e705219f04d0be75a212de
 library/posixpath.pyc	10734	d694316c239784c444b252c801c97efa
@@ -22,6 +22,6 @@
 doc/manual/hu/bugreport.png	12385	c71c7db56f19870ae5d6bb7f533d3679
 library/share/locale/hu/LC_MESSAGES/gtk20.mo	58794	6ee4491ae7bc9d3dfb778578eb29a0b3
-library/gobject/_gobject.pyc	573	15a834f99f84e8c25ded4c9b4f444d56
-locale/en/LC_MESSAGES/mlx.mo	61484	921f96175cd9e4afd7c6b2317bc90cc0
+library/gobject/_gobject.pyc	573	92d29c5d8b466d18acef25eaa9cba97f
+locale/en/LC_MESSAGES/mlx.mo	61639	aa73425f6f3c43fa83be90b9909f369e
 library/encodings/utf_7.pyc	1747	2beeb059236527197d6c640396476ce8
 doc/manual/hu/pirepViewer.png	26835	267e4325e550ed14ed22f86a2c7e8e9d
@@ -63,5 +63,5 @@
 library/encodings/utf_8.pyc	1806	4b8dee0284ea2f55c4142fcde7463524
 library/unicodedata.pyd	688128	c856c31a03dc892e4f2aee93d0bfee44
-locale/hu/LC_MESSAGES/mlx.mo	66076	971aaf421fda1cb5174dbf68b87aa8a7
+locale/hu/LC_MESSAGES/mlx.mo	66240	db0a23e9f1d4657562180946ae340f22
 library/encodings/mac_roman.pyc	2716	4b4745eccfcb77860b70b374ac07fbcc
 library/copy_reg.pyc	4893	7a58bc441232056d1c123cd3375b3774
@@ -76,5 +76,5 @@
 library/_hashlib.pyd	287232	a1a18de0644f481749fcbcf26005eeaf
 doc/manual/en/monitorWindow.png	20886	42e08d10c758e21209c4c021f391dee7
-library/_hashlib.pyc	549	3e6c18da5d87853e1652c77d4f40cf4e
+library/_hashlib.pyc	549	ec9a5e38a98eaf491bd44048fe3f8d16
 library/libpng14-14.dll	230529	f3a15497e25baaa721f96638d7a4d2f8
 library/copy.pyc	11674	30d1745c5e38ab170f8a0486fbebf305
@@ -90,5 +90,5 @@
 library/traceback.pyc	11223	50ace5c2c6c040a923082442da94cd78
 library/_weakrefset.pyc	9290	a56e87c491c0511cd329a3c8c4cd9563
-library/mlx/flight.pyc	17303	96ca499dc0257e7051e5895ca5a7a265
+library/mlx/flight.pyc	17473	8b37fd80c11c3a89738f5e4e1cda5578
 library/urllib2.pyc	43566	7eb57ea0dcd1086d1d9073a5cefb0903
 doc/manual/en/flightSelection.png	29847	7817c8f5ed8d7fe64cf72d0addaf3e4a
@@ -117,5 +117,5 @@
 library/encodings/mac_latin2.pyc	4717	564dbaf5c296a147203eb821dc2466ba
 library/encodings/utf_16_be.pyc	1854	84a1d1465453727af051eaeeb853782f
-library/mlx/fsuipc.pyc	72548	b2c4676aa9976a2f425ab624682e39aa
+library/mlx/fsuipc.pyc	72456	eafa9e3e0e9bdff01306dfb2863de12b
 library/xml/sax/__init__.pyc	3579	c78222ad1fd4f3dbdea06666fe74e84e
 doc/manual/en/connect.png	27436	5406dfe5408569cb95499c057bf0185b
@@ -135,6 +135,6 @@
 sounds/767taxi.mp3	160496	4cb43cda6e1214fbe15328a716827345
 doc/manual/en/prefsAdvanced.png	16169	64c14e0842c413cac98b92926279f3e0
-library/cairo/_cairo.pyc	561	70beff493bb08d983e44f832660acb73
-library/win32file.pyc	552	39519cf7cf18c04fc7b8b8010490f7dd
+library/cairo/_cairo.pyc	561	6ac388cead1acc49e51e05d06033ad23
+library/win32file.pyc	552	e12cc4667c80e4f5fb88cf46ec92d734
 library/encodings/cp865.pyc	7868	7d40a7ba053ac23339218be8799e5397
 library/lib/gtk-2.0/2.10.0/engines/libwimp.dll	97984	a5bbbe46e1363aba9020a76f2d58e9d6
@@ -145,5 +145,5 @@
 library/share/locale/hu/LC_MESSAGES/gtk20-properties.mo	167125	cd1b1b3693c1e3c74bfcea20be640cc6
 conn_red.png	914	860388cb7c3b26b2f348fdbee40780ce
-library/bz2.pyc	534	4a83ab9de101cd0ce5b9b866f5661e85
+library/bz2.pyc	534	5595f0a4a8ffd0b63f7542fddc918028
 library/xml/sax/expatreader.pyc	13563	4f64c0efb2f1f8dbba84cdb5c6036f2d
 library/email/utils.pyc	8840	a7be428df6fddee6121cfce55382d1f8
@@ -161,6 +161,6 @@
 library/mlx/gui/statusbar.pyc	4812	87773f7d0bd28ed6250acf1972110ca8
 library/warnings.pyc	12666	9788b5f45765e8204e04c820f2711094
-mlxupdate.exe	19456	e9a978949f7755311ad9e0db55f1e63c
-library/mlx/const.pyc	6707	9375ae285d18c54e13107e5775868192
+mlxupdate.exe	18944	b8bf9f3d22348aa1f78727f81d967f93
+library/mlx/const.pyc	6710	2187949c18be095bada1927f3127d0cf
 library/email/errors.pyc	3229	cdb78ab237e195ad69f059128bc03b1d
 library/linecache.pyc	3091	666e6f81b0ea9a7637f8068493f738d5
@@ -175,5 +175,5 @@
 library/shlex.pyc	7282	cbe1a4823894c97c87d01f9d9faf5b8f
 library/cairo/__init__.pyc	142	010dd7cc8485565d5782d60e92e4ba34
-library/_ssl.pyc	537	2cb40e7a5123a20f42dca69bbda9b17c
+library/_ssl.pyc	537	d7b3b5f87a333acdf28a28f52c5c8ce7
 library/encodings/iso8859_8.pyc	2717	f3ad6ce58cfac9e3ca86cf5c2fef092b
 library/encodings/iso2022_jp_1.pyc	1699	faed9d5747e982cec9fa8b3ad8e1de26
@@ -196,6 +196,6 @@
 library/encodings/cp1252.pyc	2676	b4c43cc72650c911cf3654dbaf565df9
 library/share/locale/hu/LC_MESSAGES/atk10.mo	8465	e3f3466da367215aad971d7b0394229d
-library/unicodedata.pyc	558	b3e806269dfb39ac4ab81ba5049f2458
-library/select.pyc	543	0608a6239838009159f223f473ae2a6a
+library/unicodedata.pyc	558	9570da5a79cb8e2156f6577185461f85
+library/select.pyc	543	ad5bc4844763975968138f81e1e9ac65
 library/encodings/uu_codec.pyc	4646	5282a5fcb676851224d2924aa3e48a9e
 library/ConfigParser.pyc	23857	1d17020dd0000ce0f73de812effa152f
@@ -217,4 +217,5 @@
 library/mlx/singleton.pyc	9184	3b71000d428959ab355db2658cea9557
 library/locale.pyc	48754	a51d20267132acc6bc66ac6252cf870f
+library/xplra.pyc	32012	50a1d9f19c0cc88783d264909ad1c678
 library/socket.pyc	15488	35f8eb455d6d8df8e19826d1dd7193f2
 sounds/notify.wav	119384	108025b17f67e61946df5d7fbf713cdb
@@ -228,5 +229,5 @@
 doc/manual/hu/flightInfoTab.png	28786	3e8af27775131f02696b0912ca441f36
 library/encodings/utf_8_sig.pyc	4625	1b1405db186e5819fa271ff96c1ca2c5
-library/pangocairo.pyc	555	5ef32a4f4e2c28fa162011c342e3b789
+library/pangocairo.pyc	555	33065d79493949ba073d7fc7b165004b
 library/encodings/cp1255.pyc	2697	b265913c887f2e1c8736632b652c0093
 library/encodings/zlib_codec.pyc	4308	319c9073aff840436a03cc1fa0b08fb5
@@ -235,5 +236,5 @@
 doc/manual/hu/connect.png	28822	929f3eb85ee3cad019e12017c923f664
 library/encodings/palmos.pyc	2876	9284ad53dcfd82dfae0a79923b1cf91d
-library/mlx/web.pyc	30343	d2e330da2cffcb9f4abc5f3b9941b2cb
+library/mlx/web.pyc	30343	0cab98a640fd3937828d24c2fe8ef0ef
 library/mlx/gui/gates.pyc	5384	9f6acad03872832505e31406a1b6762b
 library/email/feedparser.pyc	10682	4de6a63da8dc270c9881edb9c2f28508
@@ -241,5 +242,5 @@
 library/intl.dll	152489	eb2d4c4d4a527bc88a69a16cc99afcf5
 library/encodings/mbcs.pyc	1883	2213911a7dd0deada6799b63e18b695b
-library/pyuipc.pyc	543	bb664c97a351eb86af6fc2889e2cf07a
+library/pyuipc.pyc	543	c194030ac361f97edfcf756a91a949f1
 sounds/TOC.mp3	273032	fae1be2ad25c8b54806bd1d759919705
 library/encodings/iso8859_4.pyc	2678	16f2fe7994051752360c533c052b892b
@@ -265,11 +266,11 @@
 doc/manual/hu/flightSelection.png	30815	e8bcdda6079c6b16ffa72ca62c21ca85
 library/win32pipe.pyd	24064	de374eded459293b2a7eb8ed8fb38eaa
-library/win32pipe.pyc	552	3525d9ce759fb173fb5949768955a43c
+library/win32pipe.pyc	552	79ce5d61fe62ec37a212e8a854f2807b
 doc/manual/hu/cruise.png	27460	a2610173563e45baae1b0f9e63007ebd
 doc/manual/hu/logTab.png	28706	f48004202310bd2a1cb2f873d8064e93
 library/encodings/iso2022_jp_2004.pyc	1723	66376e788f3819e781897268c1100986
-library/win32event.pyc	555	062a638079612d30bbf679ada15dd79b
+library/win32event.pyc	555	63b964537d2e2157645cab4a6b90e68d
 library/encodings/cp866.pyc	8103	f36418dcf93b17b3ced63bd544f44159
-library/gio/_gio.pyc	549	332e8e3dcee26679a37beb64db0bc90b
+library/gio/_gio.pyc	549	41b7fad0fabe6f41db16a6e05d415848
 library/encodings/cp437.pyc	7874	63f1278c0a66129c6fb4f504d6b61b66
 doc/manual/hu/helpTab.png	27961	8e7cc4aeb8d7f40fcf186c7caf53583b
@@ -294,5 +295,5 @@
 library/mlx/gui/pirep.pyc	16749	7a295b28dbdad43b33c52d814be60792
 library/mimetypes.pyc	17806	927ce6a7079707aaee925e855ed23454
-library/_ctypes.pyc	546	e0cd0d88a4af0cd4307b0da1351b7c55
+library/_ctypes.pyc	546	d4c47f95473dc9d7b363d62b26db0a7e
 sounds/F70taxi.mp3	154854	51332dc691d3519355f61ed06c959d8d
 library/encodings/cp857.pyc	7611	1b46c8959ac097b73e1c680d464cdc32
@@ -312,5 +313,5 @@
 library/repr.pyc	5045	82f0d89c685c3dacd941e01e4b2f31ef
 library/quopri.pyc	6352	87c42d94f753dd4c8a761cfa79453bb8
-library/mlx/gui/monitor.pyc	12021	71dd3fb4b634cb6ac20ee889ca3a09c0
+library/mlx/gui/monitor.pyc	12214	6814c319b26ad7213e0759cf909c788d
 library/encodings/latin_1.pyc	2111	52427573ccbfd06220b67d5a9dd1376e
 library/mlx/update.pyc	16952	b4c0e85d094410aa0b7075476962730d
@@ -321,5 +322,5 @@
 doc/manual/en/gatesTab.png	29585	00ebc96a2b4a7473dbb6a3162a04192f
 library/bdb.pyc	17708	9a8fdcaa65f595c842689247847879d9
-library/mlx/gui/flight.pyc	111770	6991ddb672d43b8de56d6e8c30694ae7
+library/mlx/gui/flight.pyc	112899	536e12fba696bbc0781e2b5e73e8ea54
 library/unittest/case.pyc	37758	1d2235e74c246b562c5c00ddf73c621a
 library/subprocess.pyc	38648	c9f749db06dfc9fb32079de51d6efd01
@@ -337,5 +338,5 @@
 library/encodings/cp863.pyc	7868	fc7ff06dab536b93c68fcb8e7bac713f
 library/xml/parsers/__init__.pyc	294	83f6a77ed8cd962b3c75c4779dc557ac
-library/pyexpat.pyc	546	90f96a71a10eb1e06fe279e95e0dc139
+library/pyexpat.pyc	546	3868a2225d9e99907f2cd9d97ef9a300
 doc/manual/hu/gateSelection.png	29237	bac37e7001261a40a84e4202247f6052
 logo.png	69816	4a7517b22cfd75cc9c15e3fc1b7ebeec
@@ -363,18 +364,19 @@
 library/_socket.pyd	38400	5d273be76603399e88cf57f2605004bf
 sounds/ding.wav	80856	18e639792d3767436ac6955eb60e4f54
-library/_socket.pyc	546	4f16cf07fdd8412d1ea8228238b1556c
+library/_socket.pyc	546	e4eede84eacb1ffb8a725cfda432fb76
 library/encodings/hex_codec.pyc	3520	a8b8d5b7c8810379c7007bcbc253951c
-library/mlx/checks.pyc	58364	c7c535545956d5f434e36c67d398db5f
+library/mlx/checks.pyc	58567	d6c6eebab7a0a19c5f33007ce8b4c3e5
 doc/manual/en/statusIcon2.png	5875	71fc121823dee0a4c00715fac1b5665c
-library/mlx/sound.pyc	6326	19c542e4f40270f46e42ac087a3532b3
+library/mlx/sound.pyc	10001	78655c3a51de983d8e550934c05e1f30
 doc/manual/en/pirepViewer.png	25098	7c545e70b995ac0f9fcae93fe5d0e899
 library/encodings/aliases.pyc	8750	75c22196420d0157fec085d285947a71
-library/glib/_glib.pyc	555	d24c8e5801a398eab8516b8707049127
+library/glib/_glib.pyc	555	465b8ddc57386c9f7ef448d9cb802abd
 library/libpangowin32-1.0-0.dll	108945	d07f128828225b7cc38e9e590eb3ba8a
 library/encodings/iso8859_10.pyc	2693	5e6cc8f721486ddbc1c99da285e13191
-library/win32api.pyc	549	a5622f32da0646859ff8379bcc24a3b4
+library/win32api.pyc	549	efb111e7d3dae80fd15bf47d026e6114
 library/gobject/constants.pyc	1957	2e9cdc605e7c449ef4fc23f56128acec
 library/libgtk-win32-2.0-0.dll	4938115	cc6dca192aca6f91bd96a5b5f86e036d
 doc/manual/hu/calloutsEditor.png	16026	fede870894a85e5d21ca16a026aa6a98
+library/mlx/xplane.pyc	66878	9ee20e3efd68fd414ca3b4716c1f2985
 library/ssl.pyc	13785	d90c880699ecbedb3182f556b9733868
 library/tempfile.pyc	18526	e2cc2d63c29e15f20ee9325e790643a3
@@ -419,6 +421,6 @@
 library/libgthread-2.0-0.dll	44287	7ad6f303082b382bff7bafbab246c61f
 library/glib._glib.pyd	58368	0de636503e43c4eb00e80927bc9bda97
-library/mlx/config.pyc	29918	fca1bd8ff8948570ce4ff0a673cd99e3
-library/atk.pyc	534	58aa8ee24d8a61d0e2b15eb6b9200897
+library/mlx/config.pyc	30521	406992cc40c69fc2054e02d516088ba9
+library/atk.pyc	534	7e987d4ee2de16ac0050d9e1944a860a
 library/gio._gio.pyd	263168	acb8c95fdc391079bebdf7d8685b0dbc
 library/encodings/cp861.pyc	7868	c97a8970ce4d9f40cbafbea6576fbdca
@@ -430,7 +432,7 @@
 library/__future__.pyc	4177	43d264d951ac2085334c7b9dd4558595
 library/logging/__init__.pyc	54813	7bcdf2e8da474f5e94d8e499f6fc3ed4
-runmlx.exe	64000	7e0ddfc5d67212ce2da74aa6b1b5b08e
+runmlx.exe	63488	3060aa0ed73648edc26992e224568352
 library/optparse.pyc	51253	39594a40bc1222173d57b7d5740e3d92
-library/mlx/fs.pyc	11756	a758a6bd0c7cc28dbc9e3c0a33fe18d1
+library/mlx/fs.pyc	11803	268cda55420c4ce0750029ea203155e6
 doc/manual/en/takeoff.png	26128	63fb0227347eba88fe4f108d730dc8c0
 library/unittest/result.pyc	7444	aefbb3cc79c0ff17787ce84faed7f5e8
@@ -443,5 +445,5 @@
 library/glib/option.pyc	11891	bd90183c75d8f473ecf653e3d63a27f9
 library/StringIO.pyc	11100	f4eb4a2bb3d6fb0af925681c5375c673
-library/pango.pyc	540	52e336c3a5a39745e9a075b943482f9c
+library/pango.pyc	540	1b3d8d42905b120b95525147e2fd4b0a
 Microsoft.VC90.CRT/msvcp90.dll	568832	6de5c66e434a9c1729575763d891c6c2
 library/sre_parse.pyc	18380	fe2376a101668be1124f9ee316662bab
Index: locale/en/mlx.po
===================================================================
--- locale/en/mlx.po	(revision 512)
+++ locale/en/mlx.po	(revision 513)
@@ -470,4 +470,13 @@
 msgid "connect_gate"
 msgstr "Gate:"
+
+msgid "connect_sim"
+msgstr "Simulator:"
+
+msgid "connect_sim_msfs"
+msgstr "_MS FS"
+
+msgid "connect_sim_xplane"
+msgstr "_X-Plane"
 
 msgid "button_connect"
Index: locale/hu/mlx.po
===================================================================
--- locale/hu/mlx.po	(revision 512)
+++ locale/hu/mlx.po	(revision 513)
@@ -473,4 +473,13 @@
 msgstr "Kapu:"
 
+msgid "connect_sim"
+msgstr "Szimulátor:"
+
+msgid "connect_sim_msfs"
+msgstr "_MS FS"
+
+msgid "connect_sim_xplane"
+msgstr "_X-Plane"
+
 msgid "button_connect"
 msgstr "K_apcsolódás"
Index: setup.py
===================================================================
--- setup.py	(revision 378)
+++ setup.py	(revision 497)
@@ -66,5 +66,5 @@
       package_dir = { "" : "src" },
       packages = ["mlx", "mlx.gui"],
-      requires = ["pyuipc"],
+      requires = ["pyuipc", "xplra"],
       windows = [{ "script" : "runmlx.py",
                    "icon_resources" : [(1, "logo.ico")]},
Index: src/mlx/checks.py
===================================================================
--- src/mlx/checks.py	(revision 480)
+++ src/mlx/checks.py	(revision 496)
@@ -957,5 +957,8 @@
         """Check if the fault condition holds."""
         if flight.stage==const.STAGE_CRUISE:
-            bankLimit = 30
+            isDH8DXplane = flight.aircraftType==const.AIRCRAFT_DH8D and \
+                           (flight.fsType==const.SIM_XPLANE10 or
+                            flight.fsType==const.SIM_XPLANE9)
+            bankLimit = 35 if isDH8DXplane else 30
         elif flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
                               const.STAGE_DESCENT, const.STAGE_LANDING]:
@@ -968,6 +971,7 @@
     def logFault(self, flight, aircraft, logger, oldState, state):
         """Log the fault."""
+        message = "Bank too steep (%.1f)" % (state.bank,)
         flight.handleFault(BankChecker, state.timestamp,
-                           FaultChecker._appendDuring(flight, "Bank too steep"),
+                           FaultChecker._appendDuring(flight, message),
                            2)
 
Index: src/mlx/config.py
===================================================================
--- src/mlx/config.py	(revision 486)
+++ src/mlx/config.py	(revision 503)
@@ -236,4 +236,6 @@
         self._pirepAutoSave = False
 
+        self._defaultMSFS = os.name=="nt"
+
         self._enableSounds = not secondaryInstallation
 
@@ -471,4 +473,16 @@
         if pirepAutoSave!=self._pirepAutoSave:
             self._pirepAutoSave = pirepAutoSave
+            self._modified = True
+
+    @property
+    def defaultMSFS(self):
+        """Get if the default simulator type is MS FS."""
+        return self._defaultMSFS
+
+    @defaultMSFS.setter
+    def defaultMSFS(self, defaultMSFS):
+        """Set if the default simulator type is MS FS."""
+        if defaultMSFS!=self._defaultMSFS:
+            self._defaultMSFS = defaultMSFS
             self._modified = True
 
@@ -704,4 +718,7 @@
                 ApproachCallouts.fromConfig(config, aircraftType)
 
+        self._defaultMSFS = self._getBoolean(config, "general",
+                                             "defaultMSFS", os.name=="nt")
+
         self._modified = False
 
@@ -746,4 +763,7 @@
                    "yes" if self._pirepAutoSave else "no")
 
+        config.set("general", "defaultMSFS",
+                   "yes" if self._defaultMSFS else "no")
+
         config.add_section(Config._messageTypesSection)
         for messageType in const.messageTypes:
@@ -890,4 +910,6 @@
         print "  pirepAutoSave:", self._pirepAutoSave
 
+        print "  defaultMSFS:", self._defaultMSFS
+
         print "  enableSounds:", self._enableSounds
 
Index: src/mlx/const.py
===================================================================
--- src/mlx/const.py	(revision 506)
+++ src/mlx/const.py	(revision 509)
@@ -11,5 +11,5 @@
 
 ## The version of the program
-VERSION="0.19"
+VERSION="0.19xpl"
 
 #-------------------------------------------------------------------------------
Index: src/mlx/flight.py
===================================================================
--- src/mlx/flight.py	(revision 391)
+++ src/mlx/flight.py	(revision 430)
@@ -86,4 +86,9 @@
         """Get the flight stage."""
         return self._stage
+
+    @property
+    def fsType(self):
+        """Get the flight simulator type."""
+        return self._gui.fsType
 
     @property
Index: src/mlx/fs.py
===================================================================
--- src/mlx/fs.py	(revision 408)
+++ src/mlx/fs.py	(revision 501)
@@ -3,5 +3,9 @@
 from sound import startSound
 
+import os
+
 import fsuipc
+import xplane
+
 import threading
 import time
@@ -56,7 +60,10 @@
     FIXME: add info
     """
-    assert type in [const.SIM_MSFS9, const.SIM_MSFSX], \
-           "Only MS Flight Simulator 2004 and X are supported"
-    return fsuipc.Simulator(connectionListener, connectAttempts = 3)
+    if type in [const.SIM_MSFS9, const.SIM_MSFSX]:
+        return fsuipc.Simulator(connectionListener, connectAttempts = 3)
+    elif type in [const.SIM_XPLANE9, const.SIM_XPLANE10]:
+        return xplane.Simulator(connectionListener, connectAttempts = 3)
+    else:
+        "Only MS Flight Simulator 2004 and X or X-Plane 9 and 10 are supported"
 
 #-------------------------------------------------------------------------------
@@ -90,5 +97,5 @@
         with self._requestCondition:
             self._toQuit = True
-            self._requestCondition.notifty()
+            self._requestCondition.notify()
         self.join()
 
Index: src/mlx/gui/flight.py
===================================================================
--- src/mlx/gui/flight.py	(revision 512)
+++ src/mlx/gui/flight.py	(revision 513)
@@ -880,8 +880,10 @@
                                           completedHelp = xstr("connect_chelp"))
 
+        self._selectSimulator = os.name=="nt" or "FORCE_SELECT_SIM" in os.environ
+
         alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
                                   xscale = 0.0, yscale = 0.0)
 
-        table = gtk.Table(5, 2)
+        table = gtk.Table(7 if self._selectSimulator else 5, 2)
         table.set_row_spacings(4)
         table.set_col_spacings(16)
@@ -949,4 +951,37 @@
         labelAlignment.add(self._departureGate)
         table.attach(labelAlignment, 1, 2, 4, 5)
+
+        if self._selectSimulator:
+            labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0, yalign=0.5)
+            label = gtk.Label(xstr("connect_sim"))
+            labelAlignment.add(label)
+            table.attach(labelAlignment, 0, 1, 5, 7)
+
+            selectAlignment = gtk.Alignment(xalign=0.0, xscale=0.0, yalign=0.5)
+
+            selectBox = gtk.HBox()
+            if pygobject:
+                self._selectMSFS = \
+                  gtk.RadioButton.new_with_mnemonic_from_widget(None,
+                                                                xstr("connect_sim_msfs"))
+            else:
+                self._selectMSFS = gtk.RadioButton(None,
+                                                   xstr("connect_sim_msfs"))
+
+            selectBox.pack_start(self._selectMSFS, False, False, 0);
+
+            if pygobject:
+                self._selectXPlane = \
+                  gtk.RadioButton.new_with_mnemonic_from_widget(self._selectMSFS,
+                                                                xstr("connect_sim_xplane"))
+            else:
+                self._selectXPlane = gtk.RadioButton(self._selectMSFS,
+                                                     xstr("connect_sim_xplane"))
+
+            selectBox.pack_start(self._selectXPlane, False, False, 8);
+
+            selectAlignment.add(selectBox)
+            table.attach(selectAlignment, 1, 2, 5, 7)
+
 
         self.addCancelFlightButton()
@@ -982,4 +1017,9 @@
         self._departureGate.set_markup(gate)
 
+        if self._selectSimulator:
+            config = self._wizard.gui.config
+            self._selectMSFS.set_active(config.defaultMSFS)
+            self._selectXPlane.set_active(not config.defaultMSFS)
+
     def finalize(self):
         """Finalize the page."""
@@ -996,5 +1036,16 @@
     def _connectClicked(self, button):
         """Called when the Connect button is pressed."""
-        self._wizard._connectSimulator()
+        if self._selectSimulator:
+            simulatorType = const.SIM_MSFS9 if self._selectMSFS.get_active() \
+                                            else const.SIM_XPLANE10
+        else:
+            simulatorType = const.SIM_MSFS9 if os.name=="nt" \
+              else const.SIM_XPLANE10
+
+        config = self._wizard.gui.config
+        config.defaultMSFS = simulatorType == const.SIM_MSFS9
+        config.save()
+
+        self._wizard._connectSimulator(simulatorType)
 
     def _forwardClicked(self, button):
@@ -3573,7 +3624,8 @@
         self.gui.rtoToggled(indicated)
 
-    def _connectSimulator(self):
+    def _connectSimulator(self, simulatorType):
         """Connect to the simulator."""
-        self.gui.connectSimulator(self._bookedFlight.aircraftType)
+        self.gui.connectSimulator(self._bookedFlight.aircraftType,
+                                  simulatorType)
 
     def _arrivalMETARCallback(self, returned, result):
Index: src/mlx/gui/gui.py
===================================================================
--- src/mlx/gui/gui.py	(revision 491)
+++ src/mlx/gui/gui.py	(revision 501)
@@ -69,4 +69,5 @@
         self._flight = None
         self._simulator = None
+        self._fsType = None
         self._monitoring = False
 
@@ -208,4 +209,9 @@
 
     @property
+    def fsType(self):
+        """Get the flight simulator type."""
+        return self._fsType
+
+    @property
     def entranceExam(self):
         """Get whether an entrance exam is about to be taken."""
@@ -428,4 +434,5 @@
             self._wizard.connected(fsType, descriptor)
         self._reconnecting = False
+        self._fsType = fsType
         self._listenHotkeys()
 
@@ -512,4 +519,6 @@
         """Reset the GUI."""
         self._disconnect()
+
+        self._simulator = None
 
         self._flightInfo.reset()
@@ -887,5 +896,5 @@
             self._stdioStartingLine = False
 
-    def connectSimulator(self, aircraftType):
+    def connectSimulator(self, aircraftType, simulatorType):
         """Connect to the simulator for the first time."""
         self._logger.reset()
@@ -898,5 +907,5 @@
 
         if self._simulator is None:
-            self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
+            self._simulator = fs.createSimulator(simulatorType, self)
             fs.setupMessageSending(self.config, self._simulator)
             self._setupTimeSync()
Index: src/mlx/gui/monitor.py
===================================================================
--- src/mlx/gui/monitor.py	(revision 480)
+++ src/mlx/gui/monitor.py	(revision 496)
@@ -226,4 +226,8 @@
         table.attach(label, 0, 1, 8, 9)
         table.attach(self._qnh, 1, 2, 8, 9)
+
+        (label, self._cog) = self._createLabeledEntry("CoG:", 7)
+        table.attach(label, 2, 3, 8, 9)
+        table.attach(self._cog, 3, 4, 8, 9)
 
         alignment.add(table)
@@ -312,4 +316,5 @@
             self._adf2.set_text("-")
             self._qnh.set_text("-")
+            self._cog.set_text("-")
         else:
             self._timestamp.set_text(time.strftime("%H:%M:%S",
@@ -386,5 +391,5 @@
             self._gearControlDown.set_sensitive(aircraftState.gearControlDown)
             self._gearsDown.set_sensitive(aircraftState.gearsDown)
-            self._spoilersArmed.set_sensitive(aircraftState.spoilersArmed)
+            self._spoilersArmed.set_sensitive(aircraftState.spoilersArmed is True)
             self._spoilersExtension.set_text("%.0f" % (aircraftState.spoilersExtension,))
             self._windSpeed.set_text("%.0f" % (aircraftState.windSpeed,))
@@ -404,4 +409,5 @@
             self._adf1.set_text("-" if aircraftState.adf1 is None else aircraftState.adf1)
             self._adf2.set_text("-" if aircraftState.adf2 is None else aircraftState.adf2)
+            self._cog.set_text("%.2f%%" % (aircraftState.cog*100.0,))
 
 #------------------------------------------------------------------------------
Index: src/mlx/sound.py
===================================================================
--- src/mlx/sound.py	(revision 401)
+++ src/mlx/sound.py	(revision 422)
@@ -166,23 +166,119 @@
     def startSound(name, finishCallback = None, extra = None):
         """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, finishCallback = finishCallback,
                              extra = extra)
-        
+
 #------------------------------------------------------------------------------
 
 else: # os.name!="nt"
-    def initializeSound(soundsDirectory):
-        """Initialize the sound handling with the given directory containing
-        the sound files."""
-        pass
-
-    def startSound(name, finishCallback = None, extra = None):
-        """Start playing back the given sound.
-
-        FIXME: it does not do anything currently, but it should."""
-        print "sound.startSound:", name
+    import threading
+    try:
+        import pyglet
+
+        class SoundThread(threading.Thread):
+            """A thread executing the pyglet event loop that directs the
+            playback of the sound files."""
+            class Player(pyglet.media.ManagedSoundPlayer):
+                """Player which handles the end-of-stream condition
+                properly."""
+
+                def __init__(self, finishCallback, extra):
+                    """Construct the player with the given data."""
+                    super(SoundThread.Player, self).__init__()
+
+                    self._finishCallback = finishCallback
+                    self._extra = extra
+
+                def _on_eos(self):
+                    if self._finishCallback is not None:
+                        self._finishCallback(True, self._extra)
+                    return super(SoundThread.Player, self)._on_eos()
+
+            class EventLoop(pyglet.app.EventLoop):
+                """Own implementation of the event loop that collects the
+                requested sound files and plays them."""
+
+                def __init__(self, soundsDirectory):
+                    """Construct the event loop."""
+                    super(SoundThread.EventLoop, self).__init__()
+
+                    self._soundsDirectory = soundsDirectory
+
+                    self._lock = threading.Lock()
+                    self._requestedSounds = []
+
+                def startSound(self, name, finishCallback, extra):
+                    """Add the sound with the given name"""
+                    with self._lock:
+                        path = os.path.join(self._soundsDirectory, name)
+                        self._requestedSounds.append( (path,
+                                                       finishCallback, extra) )
+
+                def idle(self):
+                    """The idle callback."""
+                    with self._lock:
+                        requestedSounds = self._requestedSounds
+                        self._requestedSounds = []
+
+                    for (path, finishCallback, extra) in requestedSounds:
+                        try:
+                            media = pyglet.media.load(path)
+                            player = SoundThread.Player(finishCallback, extra)
+                            player.queue(media)
+                            player.play()
+                        except Exception, e:
+                            print "mlx.SoundThread.EventLoop.idle: " + str(e)
+                            if finishCallback is not None:
+                                finishCallback(False, extra)
+
+                    timeout = super(SoundThread.EventLoop, self).idle()
+                    return 0.1 if timeout is None else min(timeout, 0.1)
+
+            def __init__(self, soundsDirectory):
+                """Construct the sound playing thread with the given
+                directory."""
+                super(SoundThread, self).__init__()
+
+                self.daemon = True
+                self.eventLoop = SoundThread.EventLoop(soundsDirectory)
+
+            def run(self):
+                """Run the event loop."""
+                self.eventLoop.run()
+
+            def startSound(self, name, finishCallback = None, extra = None):
+                """Start the playback of the given sound."""
+                self.eventLoop.startSound(name, finishCallback, extra)
+
+        _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, finishCallback = None, extra = None):
+            """Start playing back the given sound."""
+            _thread.startSound(name, finishCallback = finishCallback,
+                               extra = extra)
+
+    except:
+        print "The pyglet library is missing from your system. It is needed for sound playback on Linux"
+        def initializeSound(soundsDirectory):
+            """Initialize the sound handling with the given directory containing
+            the sound files."""
+            pass
+
+        def startSound(name, finishCallback = None, extra = None):
+            """Start playing back the given sound.
+
+            FIXME: it does not do anything currently, but it should."""
+            print "sound.startSound:", name
 
 #------------------------------------------------------------------------------
@@ -192,5 +288,5 @@
     def callback(result, extra):
         print "callback", result, extra
-    
+
     initializeSound("e:\\home\\vi\\tmp")
     startSound("malev.mp3", finishCallback = callback, extra="malev.mp3")
Index: src/mlx/xplane.py
===================================================================
--- src/mlx/xplane.py	(revision 505)
+++ src/mlx/xplane.py	(revision 505)
@@ -0,0 +1,1916 @@
+
+import fs
+import const
+import util
+
+import threading
+import time
+import calendar
+import datetime
+import sys
+import codecs
+import math
+
+from xplra import XPlane, MultiGetter, MultiSetter, ProtocolException
+from xplra import TYPE_INT, TYPE_FLOAT, TYPE_DOUBLE
+from xplra import TYPE_FLOAT_ARRAY, TYPE_INT_ARRAY, TYPE_BYTE_ARRAY
+from xplra import HOTKEY_MODIFIER_SHIFT, HOTKEY_MODIFIER_CONTROL
+
+#------------------------------------------------------------------------------
+
+## @package mlx.xplane
+#
+# The module towards X-Plane
+#
+# This module implements the simulator interface to X-Plane via the
+# X-Plane Remote Access (xplra) plugin.
+
+#------------------------------------------------------------------------------
+
+_hgin2hpa = 1013.25 / 29.92
+
+_mps2knots = 3600.0 / 1852
+
+#------------------------------------------------------------------------------
+
+class Request(object):
+    """Base class for one-shot requests."""
+    def __init__(self, handler, callback, extra):
+        """Construct the request."""
+        self._handler = handler
+        self._callback = callback
+        self._extra = extra
+        self._result = None
+
+    def process(self, time):
+        """Process the request.
+
+        Return True if the request has succeeded, False if data validation
+        has failed for a reading request. An exception may also be thrown
+        if there is some lower-level communication problem."""
+        if self._process(time):
+            Handler._callSafe(lambda: self._callback(self._result,
+                                                     self._extra))
+            return True
+        else:
+            return False
+
+    def fail(self):
+        """Handle the failure of this request."""
+        Handler._callSafe(lambda: self._callback(False, self._extra))
+
+class DataRequest(Request):
+    """A simple, one-shot data read or write request."""
+    def __init__(self, handler, forWrite, data, callback, extra,
+                 validator = None):
+        """Construct the request."""
+        super(DataRequest, self).__init__(handler, callback, extra)
+
+        self._forWrite = forWrite
+        self._validator = validator
+
+        xplane = handler._xplane
+        self._multiBuffer = xplane.createMultiSetter() if forWrite \
+                            else xplane.createMultiGetter()
+
+        Handler._setupMultiBuffer(self._multiBuffer,
+                                  [(d[0], d[1]) for d in data])
+
+        if forWrite:
+            index = 0
+            for (_, _, value) in data:
+                self._multiBuffer[index] = value
+                index += 1
+
+
+    def fail(self):
+        """Handle the failure of this request."""
+        if self._forWrite:
+            super(DataRequest, self).fail()
+        else:
+            Handler._callSafe(lambda: self._callback(None, self._extra))
+
+    def _process(self, time):
+        """Process the request."""
+        if self._forWrite:
+            self._multiBuffer.execute()
+            self._result = True
+            return True
+
+        try:
+            if Handler._performRead(self._multiBuffer,
+                                    self._extra, self._validator):
+                self._result = self._multiBuffer
+                return True
+            else:
+                return False
+        except ProtocolException, e:
+            self._result = None
+            return True
+
+class ShowMessageRequest(Request):
+    """Request to show a message in the simulator window."""
+    def __init__(self, handler, message, duration, callback, extra):
+        """Construct the request."""
+        super(ShowMessageRequest, self).__init__(handler,
+                                                         callback, extra)
+        self._message = message
+        self._duration = duration
+
+    def _process(self, time):
+        """Process the request."""
+        self._handler._xplane.showMessage(self._message, self._duration)
+        self._result = True
+        return True
+
+class RegisterHotkeysRequest(Request):
+    """Request to register hotkeys with the simulator."""
+    def __init__(self, handler, hotkeyCodes, callback, extra):
+        """Construct the request."""
+        super(RegisterHotkeysRequest, self).__init__(handler,
+                                                     callback,
+                                                     extra)
+        self._hotkeyCodes = hotkeyCodes
+
+    def _process(self, time):
+        """Process the request."""
+        self._handler._xplane.registerHotkeys(self._hotkeyCodes)
+        self._result = True
+        return True
+
+class UnregisterHotkeysRequest(Request):
+    """Request to register hotkeys with the simulator."""
+    def _process(self, time):
+        """Process the request."""
+        self._handler._xplane.unregisterHotkeys()
+        self._result = True
+        return True
+
+class PeriodicRequest(object):
+    """A periodic request."""
+    def __init__(self, handler, id, period, callback, extra):
+        """Construct the periodic request."""
+        self._handler = handler
+        self._id = id
+        self._period = period
+        self._nextFire = time.time()
+        self._callback = callback
+        self._extra = extra
+        self._result = None
+
+    @property
+    def id(self):
+        """Get the ID of this periodic request."""
+        return self._id
+
+    @property
+    def nextFire(self):
+        """Get the next firing time."""
+        return self._nextFire
+
+    def process(self, now):
+        """Check if this request should be executed, and if so, do so.
+
+        now is the time at which the request is being executed. If this
+        function is called too early, nothing is done, and True is
+        returned.
+
+        Return True if the request has succeeded, False if data validation
+        has failed. An exception may also be thrown if there is some
+        lower-level communication problem."""
+        if now<self._nextFire:
+            return True
+
+        isOK = self._process(time)
+
+        if isOK:
+            Handler._callSafe(lambda: self._callback(self._result,
+                                                     self._extra))
+            now = time.time()
+            while self._nextFire <= now:
+                self._nextFire += self._period
+
+        return isOK
+
+    def fail(self):
+        """Handle the failure of this request."""
+        pass
+
+    def __cmp__(self, other):
+        """Compare two periodic requests. They are ordered by their next
+        firing times."""
+        return cmp(self._nextFire, other._nextFire)
+
+class PeriodicDataRequest(PeriodicRequest):
+    """A periodic request."""
+    def __init__(self, handler, id, period, data, callback, extra,
+                 validator):
+        """Construct the periodic request."""
+        super(PeriodicDataRequest, self).__init__(handler, id, period,
+                                                  callback, extra)
+        self._validator = validator
+        self._multiGetter = handler._xplane.createMultiGetter()
+        Handler._setupMultiBuffer(self._multiGetter, data)
+
+    def _process(self, now):
+        """Process the request."""
+        if Handler._performRead(self._multiGetter,
+                                self._extra, self._validator):
+            self._result = self._multiGetter
+            return True
+        else:
+            return False
+
+#------------------------------------------------------------------------------
+
+class HotkeysStateRequest(PeriodicRequest):
+    """Periodic hotkey query request."""
+    def _process(self, now):
+        """Process the request."""
+        self._result = self._handler._xplane.queryHotkeys()
+        return True
+
+#------------------------------------------------------------------------------
+
+class Handler(threading.Thread):
+    """The thread to handle the requests towards X-Plane."""
+    @staticmethod
+    def _callSafe(fun):
+        """Call the given function and swallow any exceptions."""
+        try:
+            return fun()
+        except Exception, e:
+            print >> sys.stderr, util.utf2unicode(str(e))
+            return None
+
+    # The number of times a read is attempted
+    NUM_READATTEMPTS = 3
+
+    # The number of connection attempts
+    NUM_CONNECTATTEMPTS = 3
+
+    # The interval between successive connect attempts
+    CONNECT_INTERVAL = 0.25
+
+    @staticmethod
+    def _setupMultiBuffer(buffer, dataSpec):
+        """Setup the given multi-dataref buffer for the given data
+        specification.
+
+        The specification is a list of tuples of two items:
+        - the name of the dataref
+        - the type of the dataref. It can be one of the following:
+          - an integer denoting the type. If it denotes an array type, the
+            length will be -1 (i.e. as many as returned when reading the
+            value), and the offset will be 0
+          - a tuple of two or three items:
+            - the first item is the type constant
+            - the second item is the length
+            - the third item is the offset, which defaults to 0."""
+        for (name, typeInfo) in dataSpec:
+            length = -1
+            offset = 0
+            type = 0
+            if isinstance(typeInfo, tuple):
+                type = typeInfo[0]
+                length = typeInfo[1]
+                offset = 0 if len(typeInfo)<3 else typeInfo[2]
+            else:
+                type = typeInfo
+
+            if type==TYPE_INT:
+                buffer.addInt(name)
+            elif type==TYPE_FLOAT:
+                buffer.addFloat(name)
+            elif type==TYPE_DOUBLE:
+                buffer.addDouble(name)
+            elif type==TYPE_FLOAT_ARRAY:
+                buffer.addFloatArray(name, length = length, offset = offset)
+            elif type==TYPE_INT_ARRAY:
+                buffer.addIntArray(name, length = length, offset = offset)
+            elif type==TYPE_BYTE_ARRAY:
+                buffer.addByteArray(name, length = length, offset = offset)
+            else:
+                raise TypeError("xplane.Handler._setupMultiBuffer: invalid type info: %s for dataref '%s'" % (typeInfo, name))
+
+
+
+    @staticmethod
+    def _performRead(multiGetter, extra, validator):
+        """Perform a read request.
+
+        If there is a validator, that will be called with the return values,
+        and if the values are wrong, the request is retried at most a certain
+        number of times.
+
+        Return True if the request has succeeded, False if validation has
+        failed during all attempts. An exception may also be thrown if there is
+        some lower-level communication problem."""
+        attemptsLeft = Handler.NUM_READATTEMPTS
+        while attemptsLeft>0:
+            try:
+                multiGetter.execute()
+            except ProtocolException, e:
+                print "xplane.Handler._performRead: " + str(e)
+                raise
+
+            if validator is None or \
+               Handler._callSafe(lambda: validator(multiGetter, extra)):
+                return True
+            else:
+                attemptsLeft -= 1
+        return False
+
+    def __init__(self, connectionListener,
+                 connectAttempts = -1, connectInterval = 0.2):
+        """Construct the handler with the given connection listener."""
+        threading.Thread.__init__(self)
+
+        self._connectionListener = connectionListener
+        self._connectAttempts = connectAttempts
+        self._connectInterval = connectInterval
+
+        self._xplane = XPlane()
+
+        self._requestCondition = threading.Condition()
+        self._connectionRequested = False
+        self._connected = False
+
+        self._requests = []
+        self._nextPeriodicID = 1
+        self._periodicRequests = []
+
+        self.daemon = True
+
+    def requestRead(self, data, callback, extra = None, validator = None):
+        """Request the reading of some data.
+
+        data is a list of tuples of the following items:
+        - the offset of the data as an integer
+        - the type letter of the data as a string
+
+        callback is a function that receives two pieces of data:
+        - the values retrieved or None on error
+        - the extra parameter
+
+        It will be called in the handler's thread!
+        """
+        with self._requestCondition:
+            self._requests.append(DataRequest(self, False, data,
+                                              callback, extra,
+                                              validator))
+            self._requestCondition.notify()
+
+    def requestWrite(self, data, callback, extra = None):
+        """Request the writing of some data.
+
+        data is a list of tuples of the following items:
+        - the offset of the data as an integer
+        - the type letter of the data as a string
+        - the data to write
+
+        callback is a function that receives two pieces of data:
+        - a boolean indicating if writing was successful
+        - the extra data
+        It will be called in the handler's thread!
+        """
+        with self._requestCondition:
+            request = DataRequest(self, True, data, callback, extra)
+            #print "xplane.Handler.requestWrite", request
+            self._requests.append(request)
+            self._requestCondition.notify()
+
+    def requestPeriodicRead(self, period, data, callback, extra = None,
+                            validator = None):
+        """Request a periodic read of data.
+
+        period is a floating point number with the period in seconds.
+
+        This function returns an identifier which can be used to cancel the
+        request."""
+        with self._requestCondition:
+            id = self._nextPeriodicID
+            self._nextPeriodicID += 1
+            request = PeriodicDataRequest(self, id, period,
+                                          data, callback,
+                                          extra, validator)
+            self._periodicRequests.append(request)
+            self._requestCondition.notify()
+            return id
+
+    def clearPeriodic(self, id):
+        """Clear the periodic request with the given ID."""
+        with self._requestCondition:
+            for i in range(0, len(self._periodicRequests)):
+                if self._periodicRequests[i].id==id:
+                    del self._periodicRequests[i]
+                    return True
+        return False
+
+    def requestShowMessage(self, message, duration, callback, extra = None):
+        """Request showing a message in the simulator."""
+        with self._requestCondition:
+            self._requests.append(ShowMessageRequest(self,
+                                                     message, duration,
+                                                     callback, extra))
+            self._requestCondition.notify()
+
+    def registerHotkeys(self, hotkeys, callback, extra = None):
+        """Request registering the given hotkeys."""
+        with self._requestCondition:
+            self._requests.append(RegisterHotkeysRequest(self, hotkeys,
+                                                         callback, extra))
+            self._requestCondition.notify()
+
+    def requestHotkeysState(self, period, callback, extra = None):
+        """Request a periodic query of the hotkey status."""
+        with self._requestCondition:
+            id = self._nextPeriodicID
+            self._nextPeriodicID += 1
+            request = HotkeysStateRequest(self, id, period, callback, extra)
+            self._periodicRequests.append(request)
+            self._requestCondition.notify()
+            return id
+
+    def unregisterHotkeys(self, callback, extra = None):
+        """Request unregistering the hotkeys."""
+        with self._requestCondition:
+            self._requests.append(UnregisterHotkeysRequest(self,
+                                                           callback, extra))
+            self._requestCondition.notify()
+
+    def connect(self):
+        """Initiate the connection to the flight simulator."""
+        with self._requestCondition:
+            if not self._connectionRequested:
+                self._connectionRequested = True
+                self._requestCondition.notify()
+
+    def disconnect(self):
+        """Disconnect from the flight simulator."""
+        with self._requestCondition:
+            self._requests = []
+            if self._connectionRequested:
+                self._connectionRequested = False
+                self._requestCondition.notify()
+
+    def clearRequests(self):
+        """Clear the outstanding one-shot requests."""
+        with self._requestCondition:
+            self._requests = []
+
+    def run(self):
+        """Perform the operation of the thread."""
+        while True:
+            self._waitConnectionRequest()
+
+            if self._connect()>0:
+                self._handleConnection()
+
+            self._disconnect()
+
+    def _waitConnectionRequest(self):
+        """Wait for a connection request to arrive."""
+        with self._requestCondition:
+            while not self._connectionRequested:
+                self._requestCondition.wait()
+
+    def _connect(self, autoReconnection = False, attempts = 0):
+        """Try to connect to the flight simulator via XPLRA
+
+        Returns True if the connection has been established, False if it was
+        not due to no longer requested.
+        """
+        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
+                self._xplane.connect()
+
+                (xplaneVersion, xplmVersion, xplraVersion) = \
+                  self._xplane.getVersions()
+
+                description = "(X-Plane version: %d, XPLM version: %d, XPLRA version: %03d)" % \
+                  (xplaneVersion, xplmVersion, xplraVersion)
+                if not autoReconnection:
+                    fsType = const.SIM_XPLANE10 if xplaneVersion>=10000 else const.SIM_XPLANE9
+
+                    Handler._callSafe(lambda:
+                                      self._connectionListener.connected(fsType,
+                                                                         description))
+                self._connected = True
+                return attempts
+            except Exception, e:
+                print "xplane.Handler._connect: connection failed: " + \
+                      util.utf2unicode(str(e)) + \
+                      " (attempts: %d)" % (attempts,)
+                if attempts<self.NUM_CONNECTATTEMPTS:
+                    time.sleep(self.CONNECT_INTERVAL)
+                self._xplane.disconnect()
+
+    def _handleConnection(self):
+        """Handle a living connection."""
+        with self._requestCondition:
+            while self._connectionRequested:
+                self._processRequests()
+                self._waitRequest()
+
+    def _waitRequest(self):
+        """Wait for the time of the next request.
+
+        Returns also, if the connection is no longer requested.
+
+        Should be called with the request condition lock held."""
+        while self._connectionRequested:
+            timeout = None
+            if self._periodicRequests:
+                self._periodicRequests.sort()
+                timeout = self._periodicRequests[0].nextFire - time.time()
+
+            if self._requests or \
+               (timeout is not None and timeout <= 0.0):
+                return
+
+            self._requestCondition.wait(timeout)
+
+    def _disconnect(self):
+        """Disconnect from the flight simulator."""
+        print "xplane.Handler._disconnect"
+        if self._connected:
+            try:
+                self._xplane.disconnect()
+            except:
+                pass
+
+            self._connected = False
+
+    def _processRequest(self, request, time, attempts):
+        """Process the given request.
+
+        If an exception occurs or invalid data is read too many times, we try
+        to reconnect.
+
+        This function returns only if the request has succeeded, or if a
+        connection is no longer requested.
+
+        This function is called with the request lock held, but is relased
+        whole processing the request and reconnecting."""
+        self._requestCondition.release()
+
+        #print "xplane.Handler._processRequest", request
+
+        needReconnect = False
+        try:
+            try:
+                if not request.process(time):
+                    print "xplane.Handler._processRequest: X-Plane returned invalid data too many times, reconnecting"
+                    needReconnect = True
+            except Exception as e:
+                print "xplane.Handler._processRequest: X-Plane connection failed (" + \
+                      util.utf2unicode(str(e)) + \
+                      "), reconnecting (attempts=%d)." % (attempts,)
+                needReconnect = True
+
+            if needReconnect:
+                with self._requestCondition:
+                    self._requests.insert(0, request)
+                self._disconnect()
+                return self._connect(autoReconnection = True, attempts = attempts)
+            else:
+                return 0
+        finally:
+            self._requestCondition.acquire()
+
+    def _processRequests(self):
+        """Process any pending requests.
+
+        Will be called with the request lock held."""
+        attempts = 0
+        while self._connectionRequested and self._periodicRequests:
+            self._periodicRequests.sort()
+            request = self._periodicRequests[0]
+
+            t = time.time()
+
+            if request.nextFire>t:
+                break
+
+            attempts = self._processRequest(request, t, attempts)
+
+        while self._connectionRequested and self._requests:
+            request = self._requests[0]
+            del self._requests[0]
+
+            attempts = self._processRequest(request, None, attempts)
+
+        return self._connectionRequested
+
+#------------------------------------------------------------------------------
+
+class Simulator(object):
+    """The simulator class representing the interface to the flight simulator
+    via XPLRA."""
+    # The basic data that should be queried all the time once we are connected
+    timeData = [ ("sim/time/local_date_days", TYPE_INT),
+                 ("sim/time/zulu_time_sec", TYPE_FLOAT) ]
+
+    normalData = timeData + \
+                 [ ("sim/aircraft/view/acf_tailnum", TYPE_BYTE_ARRAY),
+                   ("sim/aircraft/view/acf_author", TYPE_BYTE_ARRAY),
+                   ("sim/aircraft/view/acf_descrip", TYPE_BYTE_ARRAY),
+                   ("sim/aircraft/view/acf_notes", TYPE_BYTE_ARRAY),
+                   ("sim/aircraft/view/acf_ICAO", TYPE_BYTE_ARRAY),
+                   ("sim/aircraft/view/acf_livery_path", TYPE_BYTE_ARRAY) ]
+
+    flareData1 = [ ("sim/time/zulu_time_sec", TYPE_FLOAT),
+                   ("sim/flightmodel/position/y_agl", TYPE_FLOAT),
+                   ("sim/flightmodel/position/vh_ind_fpm2", TYPE_FLOAT) ]
+
+    flareStartData = [ ("sim/weather/wind_speed_kt[0]", TYPE_FLOAT),
+                       ("sim/weather/wind_direction_degt[0]", TYPE_FLOAT),
+                       ("sim/weather/visibility_reported_m", TYPE_FLOAT) ]
+
+    flareData2 = [ ("sim/time/zulu_time_sec", TYPE_FLOAT),
+                   ("sim/flightmodel/failures/onground_any", TYPE_INT),
+                   ("sim/flightmodel/position/vh_ind_fpm2", TYPE_FLOAT),
+                   ("sim/flightmodel/position/indicated_airspeed2",
+                    TYPE_FLOAT),
+                   ("sim/flightmodel/position/theta", TYPE_FLOAT),
+                   ("sim/flightmodel/position/phi", TYPE_FLOAT),
+                   ("sim/flightmodel/position/psi", TYPE_FLOAT) ]
+
+    TIME_SYNC_INTERVAL = 3.0
+
+    @staticmethod
+    def _getTimestamp(data):
+        """Convert the given data into a timestamp."""
+        year = datetime.date.today().year
+        timestamp = calendar.timegm(time.struct_time([year,
+                                                      1, 1, 0, 0, 0, -1, 1, 0]))
+        timestamp += data[0] * 24 * 3600
+        timestamp += data[1]
+
+        return timestamp
+
+    @staticmethod
+    def _getHotkeyCode(hotkey):
+        """Get the hotkey code for the given hot key."""
+        code = ord(hotkey.key)
+        if hotkey.shift: code |= HOTKEY_MODIFIER_SHIFT
+        if hotkey.ctrl: code |= HOTKEY_MODIFIER_CONTROL
+        return code
+
+    def __init__(self, connectionListener, connectAttempts = -1,
+                 connectInterval = 0.2):
+        """Construct the simulator.
+
+        The aircraft object passed must provide the following members:
+        - type: one of the AIRCRAFT_XXX constants from const.py
+        - modelChanged(aircraftName, modelName): called when the model handling
+        the aircraft has changed.
+        - handleState(aircraftState): handle the given state.
+        - flareStarted(windSpeed, windDirection, visibility, flareStart,
+                       flareStartFS): called when the flare has
+          started. windSpeed is in knots, windDirection is in degrees and
+          visibility is in metres. flareStart and flareStartFS are two time
+          values expressed in seconds that can be used to calculate the flare
+          time.
+       - flareFinished(flareEnd, flareEndFS, tdRate, tdRateCalculatedByFS,
+                       ias, pitch, bank, heading): called when the flare has
+         finished, i.e. the aircraft is on the ground. flareEnd and flareEndFS
+         are the two time values corresponding to the touchdown time. tdRate is
+         the touch-down rate, tdRateCalculatedBySim indicates if the data comes
+         from the simulator or was calculated by the adapter. The other data
+         are self-explanatory and expressed in their 'natural' units."""
+        self._fsType = None
+        self._aircraft = None
+
+        self._handler = Handler(self,
+                                connectAttempts = connectAttempts,
+                                connectInterval = connectInterval)
+        self._connectionListener = connectionListener
+        self._handler.start()
+
+        self._syncTime = False
+        self._nextSyncTime = -1
+
+        self._normalRequestID = None
+
+        self._monitoringRequested = False
+        self._monitoring = False
+
+        self._aircraftInfo = None
+        self._aircraftModel = None
+
+        self._flareRequestID = None
+        self._flareRates = []
+        self._flareStart = None
+        self._flareStartFS = None
+
+        self._hotkeyLock = threading.Lock()
+        self._hotkeyCodes = None
+        self._hotkeySetID = 0
+        self._hotkeySetGeneration = 0
+        self._hotkeyOffets = None
+        self._hotkeyRequestID = None
+        self._hotkeyCallback = None
+
+        self._latin1decoder = codecs.getdecoder("iso-8859-1")
+        self._fuelCallback = None
+
+        self._hasXFMC = None
+
+    @property
+    def hasXFMC(self):
+        """Indicate if the simulator has the X-FMC plugin."""
+        return self._hasXFMC
+
+    def connect(self, aircraft):
+        """Initiate a connection to the simulator."""
+        self._aircraft = aircraft
+        self._aircraftInfo = None
+        self._aircraftModel = None
+        self._handler.connect()
+        if self._normalRequestID is None:
+            self._nextSyncTime = -1
+            self._startDefaultNormal()
+
+    def reconnect(self):
+        """Initiate a reconnection to the simulator.
+
+        It does not reset already set up data, just calls connect() on the
+        handler."""
+        self._handler.connect()
+
+    def requestZFW(self, callback):
+        """Send a request for the ZFW."""
+        data = [ ("sim/aircraft/weight/acf_m_empty", TYPE_FLOAT),
+                 ("sim/flightmodel/weight/m_fixed", TYPE_FLOAT) ]
+        self._handler.requestRead(data, self._handleZFW, extra = callback)
+
+    def requestWeights(self, callback):
+        """Request the following weights: DOW, ZFW, payload.
+
+        These values will be passed to the callback function in this order, as
+        separate arguments."""
+        data = [ ("sim/aircraft/weight/acf_m_empty", TYPE_FLOAT),
+                 ("sim/flightmodel/weight/m_fixed", TYPE_FLOAT),
+                 ("sim/flightmodel/weight/m_total", TYPE_FLOAT) ]
+        self._handler.requestRead(data, self._handleWeights,
+                                  extra = callback)
+
+    def requestTime(self, callback):
+        """Request the time from the simulator."""
+        self._handler.requestRead(Simulator.timeData, self._handleTime,
+                                  extra = callback)
+
+    def startMonitoring(self):
+        """Start the periodic monitoring of the aircraft and pass the resulting
+        state to the aircraft object periodically."""
+        assert not self._monitoringRequested
+        self._monitoringRequested = True
+
+    def stopMonitoring(self):
+        """Stop the periodic monitoring of the aircraft."""
+        assert self._monitoringRequested
+        self._monitoringRequested = False
+
+    def startFlare(self):
+        """Start monitoring the flare time.
+
+        At present it is assumed to be called from the handler thread, hence no
+        protection."""
+        #self._aircraft.logger.debug("startFlare")
+        if self._flareRequestID is None:
+            self._flareRates = []
+            self._flareRequestID = \
+              self._handler.requestPeriodicRead(0.1,
+                                                Simulator.flareData1,
+                                                self._handleFlare1)
+
+    def cancelFlare(self):
+        """Cancel monitoring the flare time.
+
+        At present it is assumed to be called from the handler thread, hence no
+        protection."""
+        if self._flareRequestID is not None:
+            self._handler.clearPeriodic(self._flareRequestID)
+            self._flareRequestID = None
+
+    def sendMessage(self, message, duration = 3,
+                    _disconnect = False):
+        """Send a message to the pilot via the simulator.
+
+        duration is the number of seconds to keep the message displayed."""
+        print "xplra.Simulator.sendMessage:", message
+        self._handler.requestShowMessage(message, duration,
+                                         self._handleMessageSent,
+                                         extra = _disconnect)
+
+    def getFuel(self, callback):
+        """Get the fuel information for the current model.
+
+        The callback will be called with a list of triplets with the following
+        items:
+        - the fuel tank identifier
+        - the current weight of the fuel in the tank (in kgs)
+        - the current total capacity of the tank (in kgs)."""
+        if self._aircraftModel is None:
+            self._fuelCallback = callback
+        else:
+            self._aircraftModel.getFuel(self._handler, callback)
+
+    def setFuelLevel(self, levels):
+        """Set the fuel level to the given ones.
+
+        levels is an array of two-tuples, where each tuple consists of the
+        following:
+        - the const.FUELTANK_XXX constant denoting the tank that must be set,
+        - the requested level of the fuel as a floating-point value between 0.0
+        and 1.0."""
+        if self._aircraftModel is not None:
+            self._aircraftModel.setFuelLevel(self._handler, levels)
+
+    def enableTimeSync(self):
+        """Enable the time synchronization."""
+        self._nextSyncTime = -1
+        self._syncTime = True
+
+    def disableTimeSync(self):
+        """Enable the time synchronization."""
+        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._hotkeyCodes is None
+
+            self._hotkeyCodes = \
+              [self._getHotkeyCode(hotkey) for hotkey in hotkeys]
+            self._hotkeySetID += 1
+            self._hotkeySetGeneration = 0
+            self._hotkeyCallback = callback
+
+            self._handler.registerHotkeys(self._hotkeyCodes,
+                                          self._handleHotkeysRegistered,
+                                          (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._hotkeyCodes is not None:
+                self._hotkeyCodes = None
+                self._hotkeySetID += 1
+                self._hotkeyCallback = None
+                self._clearHotkeyRequest()
+
+    def disconnect(self, closingMessage = None, duration = 3):
+        """Disconnect from the simulator."""
+        assert not self._monitoringRequested
+
+        print "xplra.Simulator.disconnect", closingMessage, duration
+
+        self._stopNormal()
+        self.clearHotkeys()
+        if closingMessage is None:
+            self._handler.disconnect()
+        else:
+            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."""
+        self._fsType = fsType
+
+        self._handler.requestRead([("xfmc/Status", TYPE_INT)],
+                                  self._xfmcStatusRead)
+
+        with self._hotkeyLock:
+            if self._hotkeyCodes is not None:
+                self._hotkeySetGeneration += 1
+
+                self._handler.registerHotkeys(self._hotkeyCodes,
+                                              self._handleHotkeysRegistered,
+                                              (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."""
+        self._hasXFMC = None
+        with self._hotkeyLock:
+            self._clearHotkeyRequest()
+        self._connectionListener.disconnected()
+
+    def _startDefaultNormal(self):
+        """Start the default normal periodic request."""
+        assert self._normalRequestID is None
+        self._normalRequestID = \
+             self._handler.requestPeriodicRead(1.0,
+                                               Simulator.normalData,
+                                               self._handleNormal)
+
+    def _stopNormal(self):
+        """Stop the normal period request."""
+        assert self._normalRequestID is not None
+        self._handler.clearPeriodic(self._normalRequestID)
+        self._normalRequestID = None
+        self._monitoring = False
+
+    def _xfmcStatusRead(self, data, extra):
+        """Called when the xfmc/Status dataref is read or not."""
+        self._hasXFMC = data is not None
+        print "xplane.Simulator: XFMC is %savailable" % \
+          ("" if self._hasXFMC else "not ")
+
+    def _handleNormal(self, data, extra):
+        """Handle the reply to the normal request.
+
+        At the beginning the result consists the data for normalData. When
+        monitoring is started, it contains the result also for the
+        aircraft-specific values.
+        """
+        timestamp = Simulator._getTimestamp(data)
+
+        createdNewModel = self._setAircraftName(timestamp,
+                                                data.getString(2),
+                                                data.getString(3),
+                                                data.getString(4),
+                                                data.getString(5),
+                                                data.getString(6),
+                                                data.getString(7))
+        if self._fuelCallback is not None:
+            self._aircraftModel.getFuel(self._handler, self._fuelCallback)
+            self._fuelCallback = None
+
+        if self._monitoringRequested and not self._monitoring:
+            self._stopNormal()
+            self._startMonitoring()
+        elif self._monitoring and not self._monitoringRequested:
+            self._stopNormal()
+            self._startDefaultNormal()
+        elif self._monitoring and self._aircraftModel is not None and \
+             not createdNewModel:
+            aircraftState = self._aircraftModel.getAircraftState(self._aircraft,
+                                                                 timestamp, data)
+
+            self._aircraft.handleState(aircraftState)
+
+    def _setAircraftName(self, timestamp, tailnum, author, description,
+                         notes, icao, liveryPath):
+        """Set the name of the aicraft and if it is different from the
+        previous, create a new model for it.
+
+        If so, also notifty the aircraft about the change.
+
+        Return if a new model was created."""
+        author = self._latin1decoder(author)[0]
+        description = self._latin1decoder(description)[0]
+        notes = self._latin1decoder(notes)[0]
+        liveryPath = self._latin1decoder(liveryPath)[0]
+
+        aircraftInfo = (tailnum, author, description, notes, icao, liveryPath)
+        if aircraftInfo==self._aircraftInfo:
+            return False
+
+        print "xplane.Simulator: new data: %s, %s, %s, %s, %s, %s" % \
+              (tailnum, author, description, notes, icao, liveryPath)
+
+        self._aircraftInfo = aircraftInfo
+        needNew = self._aircraftModel is None
+        needNew = needNew or\
+            not self._aircraftModel.doesHandle(self._aircraft, aircraftInfo)
+        if not needNew:
+            specialModel = AircraftModel.findSpecial(self._aircraft,
+                                                     aircraftInfo)
+            needNew = specialModel is not None and \
+                specialModel is not self._aircraftModel.__class__
+
+        if needNew:
+            self._setAircraftModel(AircraftModel.create(self._aircraft,
+                                                        aircraftInfo))
+
+        self._aircraft.modelChanged(timestamp, description,
+                                    self._aircraftModel.name)
+
+        return needNew
+
+    def _setAircraftModel(self, model):
+        """Set a new aircraft model.
+
+        It will be queried for the data to monitor and the monitoring request
+        will be replaced by a new one."""
+        self._aircraftModel = model
+        model.simulator = self
+
+        if self._monitoring:
+            self._stopNormal()
+            self._startMonitoring()
+
+    def _startMonitoring(self):
+        """Start monitoring with the current aircraft model."""
+        data = Simulator.normalData[:]
+        self._aircraftModel.addMonitoringData(data, self._fsType)
+
+        self._normalRequestID = \
+            self._handler.requestPeriodicRead(1.0, data,
+                                              self._handleNormal)
+        self._monitoring = True
+
+    def _addFlareRate(self, data):
+        """Append a flare rate to the list of last rates."""
+        if len(self._flareRates)>=3:
+            del self._flareRates[0]
+        self._flareRates.append(data)
+
+    def _handleFlare1(self, data, normal):
+        """Handle the first stage of flare monitoring."""
+        #self._aircraft.logger.debug("handleFlare1: " + str(data))
+        if data[1]<=50.0*0.3048:
+            self._flareStart = time.time()
+            self._flareStartFS = data[0]
+            self._handler.clearPeriodic(self._flareRequestID)
+            self._handler.requestRead(Simulator.flareStartData,
+                                      self._handleFlareStart)
+
+        self._addFlareRate(data[2])
+
+    def _handleFlareStart(self, data, extra):
+        """Handle the data need to notify the aircraft about the starting of
+        the flare."""
+        #self._aircraft.logger.debug("handleFlareStart: " + str(data))
+        if data is not None:
+            windDirection = data[1]
+            if windDirection<0.0: windDirection += 360.0
+            self._aircraft.flareStarted(data[0], windDirection, data[2],
+                                        self._flareStart, self._flareStartFS)
+
+        self._flareRequestID = \
+          self._handler.requestPeriodicRead(0.1,
+                                            Simulator.flareData2,
+                                            self._handleFlare2)
+
+    def _handleFlare2(self, data, normal):
+        """Handle the first stage of flare monitoring."""
+        #self._aircraft.logger.debug("handleFlare2: " + str(data))
+        if data[1]!=0:
+            flareEnd = time.time()
+            self._handler.clearPeriodic(self._flareRequestID)
+            self._flareRequestID = None
+
+            flareEndFS = data[0]
+            if flareEndFS<self._flareStartFS:
+                flareEndFS += 86400.0
+
+            tdRate = min(self._flareRates)
+            tdRateCalculatedByFS = False
+
+            heading = data[6]
+            if heading<0.0: heading += 360.0
+
+            self._aircraft.flareFinished(flareEnd, flareEndFS,
+                                         tdRate, tdRateCalculatedByFS,
+                                         data[3], data[4], data[5], heading)
+        else:
+            self._addFlareRate(data[2])
+
+    def _handleZFW(self, data, callback):
+        """Callback for a ZFW retrieval request."""
+        zfw = data[0] + data[1]
+        callback(zfw)
+
+    def _handleTime(self, data, callback):
+        """Callback for a time retrieval request."""
+        callback(Simulator._getTimestamp(data))
+
+    def _handleWeights(self, data, callback):
+        """Callback for the weights retrieval request."""
+        dow = data[0]
+        payload = data[1]
+        zfw = dow + payload
+        grossWeight = data[2]
+        callback(dow, payload, zfw, grossWeight)
+
+    def _handleMessageSent(self, success, disconnect):
+        """Callback for a message sending request."""
+        #print "xplra.Simulator._handleMessageSent", disconnect
+        if disconnect:
+            self._handler.disconnect()
+
+    def _handleHotkeysRegistered(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:
+                self._hotkeyRequestID = \
+                  self._handler.requestHotkeysState(0.5,
+                                                    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]:
+                hotkeysPressed.append(i)
+
+        if hotkeysPressed:
+            callback(id, hotkeysPressed)
+
+    def _clearHotkeyRequest(self):
+        """Clear the hotkey request in the handler if there is any."""
+        if self._hotkeyRequestID is not None:
+            self._handler.unregisterHotkeys(self._hotkeysUnregistered)
+            self._handler.clearPeriodic(self._hotkeyRequestID)
+            self._hotkeyRequestID = None
+
+    def _hotkeysUnregistered(self, result, extra):
+        """Called when the hotkeys have been unregistered."""
+        pass
+
+#------------------------------------------------------------------------------
+
+class AircraftModel(object):
+    """Base class for the aircraft models.
+
+    Aircraft models handle the data arriving from X-Plane and turn it into an
+    object describing the aircraft's state."""
+    monitoringData = [ ("paused",
+                        "sim/time/paused", TYPE_INT),
+                       ("latitude",
+                        "sim/flightmodel/position/latitude", TYPE_DOUBLE),
+                       ("longitude",
+                        "sim/flightmodel/position/longitude", TYPE_DOUBLE),
+                       ("replay",
+                        "sim/operation/prefs/replay_mode", TYPE_INT),
+                       ("overspeed",
+                        "sim/flightmodel/failures/over_vne", TYPE_INT),
+                       ("stalled",
+                        "sim/flightmodel/failures/stallwarning", TYPE_INT),
+                       ("onTheGround",
+                        "sim/flightmodel/failures/onground_any", TYPE_INT),
+                       ("emptyWeight",
+                        "sim/aircraft/weight/acf_m_empty", TYPE_FLOAT),
+                       ("payloadWeight",
+                        "sim/flightmodel/weight/m_fixed", TYPE_FLOAT),
+                       ("grossWeight",
+                        "sim/flightmodel/weight/m_total", TYPE_FLOAT),
+                       ("heading",
+                        "sim/flightmodel/position/psi", TYPE_FLOAT),
+                       ("pitch",
+                        "sim/flightmodel/position/theta", TYPE_FLOAT),
+                       ("bank",
+                        "sim/flightmodel/position/phi", TYPE_FLOAT),
+                       ("ias",
+                        "sim/flightmodel/position/indicated_airspeed2",
+                        TYPE_FLOAT),
+                       ("mach",
+                        "sim/flightmodel/misc/machno", TYPE_FLOAT),
+                       ("groundSpeed",
+                        "sim/flightmodel/position/groundspeed", TYPE_FLOAT),
+                       ("vs",
+                        "sim/flightmodel/position/vh_ind_fpm2", TYPE_FLOAT),
+                       ("radioAltitude",
+                        "sim/flightmodel/position/y_agl", TYPE_FLOAT),
+                       ("altitude",
+                        "sim/flightmodel/position/elevation", TYPE_FLOAT),
+                       ("gLoad",
+                        "sim/flightmodel/forces/g_nrml", TYPE_FLOAT),
+                       ("flapsControl",
+                        "sim/flightmodel/controls/flaprqst", TYPE_FLOAT),
+                       ("flapsLeft",
+                        "sim/flightmodel/controls/flaprat", TYPE_FLOAT),
+                       ("flapsRight",
+                        "sim/flightmodel/controls/flap2rat", TYPE_FLOAT),
+                       ("navLights",
+                        "sim/cockpit/electrical/nav_lights_on", TYPE_INT),
+                       ("beaconLights",
+                        "sim/cockpit/electrical/beacon_lights_on", TYPE_INT),
+                       ("strobeLights",
+                        "sim/cockpit/electrical/strobe_lights_on", TYPE_INT),
+                       ("landingLights",
+                        "sim/cockpit/electrical/landing_lights_on", TYPE_INT),
+                       ("pitot",
+                        "sim/cockpit/switches/pitot_heat_on", TYPE_INT),
+                       ("parking",
+                        "sim/flightmodel/controls/parkbrake", TYPE_FLOAT),
+                       ("gearControl",
+                        "sim/cockpit2/controls/gear_handle_down", TYPE_INT),
+                       ("noseGear",
+                        "sim/flightmodel2/gear/deploy_ratio",
+                        (TYPE_FLOAT_ARRAY, 1)),
+                       ("spoilers",
+                        "sim/flightmodel/controls/lsplrdef", TYPE_FLOAT),
+                       ("altimeter",
+                        "sim/cockpit/misc/barometer_setting", TYPE_FLOAT),
+                       ("qnh",
+                        "sim/physics/earth_pressure_p", TYPE_FLOAT),
+                       ("nav1",
+                        "sim/cockpit/radios/nav1_freq_hz", TYPE_INT),
+                       ("nav1_obs",
+                        "sim/cockpit/radios/nav1_obs_degm", TYPE_FLOAT),
+                       ("nav2",
+                        "sim/cockpit/radios/nav2_freq_hz", TYPE_INT),
+                       ("nav2_obs",
+                        "sim/cockpit/radios/nav2_obs_degm", TYPE_FLOAT),
+                       ("adf1",
+                        "sim/cockpit/radios/adf1_freq_hz", TYPE_INT),
+                       ("adf2",
+                        "sim/cockpit/radios/adf2_freq_hz", TYPE_INT),
+                       ("squawk",
+                        "sim/cockpit/radios/transponder_code", TYPE_INT),
+                       ("windSpeed",
+                        "sim/weather/wind_speed_kt[0]", TYPE_FLOAT),
+                       ("windDirection",
+                        "sim/weather/wind_direction_degt[0]", TYPE_FLOAT),
+                       ("visibility",
+                        "sim/weather/visibility_reported_m", TYPE_FLOAT),
+                       ("cog",
+                        "sim/flightmodel/misc/cgz_ref_to_default", TYPE_FLOAT),
+                       ("xpdrC",
+                        "sim/cockpit/radios/transponder_mode", TYPE_INT),
+                       ("apMaster",
+                        "sim/cockpit/autopilot/autopilot_mode", TYPE_INT),
+                       ("apState",
+                        "sim/cockpit/autopilot/autopilot_state", TYPE_INT),
+                       ("apHeading",
+                        "sim/cockpit/autopilot/heading_mag", TYPE_FLOAT),
+                       ("apAltitude",
+                        "sim/cockpit/autopilot/altitude", TYPE_FLOAT),
+                       ("elevatorTrim",
+                        "sim/flightmodel/controls/elv_trim", TYPE_FLOAT),
+                       ("antiIceOn",
+                        "sim/cockpit/switches/anti_ice_on", TYPE_INT),
+                       ("surfaceHeat",
+                        "sim/cockpit/switches/anti_ice_surf_heat", TYPE_INT),
+                       ("propHeat",
+                        "sim/cockpit/switches/anti_ice_prop_heat", TYPE_INT) ]
+
+    specialModels = []
+
+    @staticmethod
+    def registerSpecial(clazz):
+        """Register the given class as a special model."""
+        AircraftModel.specialModels.append(clazz)
+
+    @staticmethod
+    def findSpecial(aircraft, aircraftInfo):
+        for specialModel in AircraftModel.specialModels:
+            if specialModel.doesHandle(aircraft, aircraftInfo):
+                return specialModel
+        return None
+
+    @staticmethod
+    def create(aircraft, aircraftInfo):
+        """Create the model for the given aircraft name, and notify the
+        aircraft about it."""
+        specialModel = AircraftModel.findSpecial(aircraft, aircraftInfo)
+        if specialModel is not None:
+            return specialModel()
+        if aircraft.type in _genericModels:
+            return _genericModels[aircraft.type]()
+        else:
+            return GenericModel()
+
+    @staticmethod
+    def _convertFrequency(value):
+        """Convert the given frequency value into a string."""
+        return "%.2f" % (value/100.0,)
+
+    @staticmethod
+    def _convertOBS(value):
+        """Convert the given OBS value into an integer."""
+        while value<0.0:
+            value += 360.0
+        return int(round(value))
+
+    def __init__(self, flapsNotches):
+        """Construct the aircraft model.
+
+        flapsNotches is a list of degrees of flaps that are available on the aircraft."""
+        self._flapsNotches = flapsNotches
+        self._simulator = None
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/Generic"
+
+    @property
+    def simulator(self):
+        """Get the simulator this aircraft model works for."""
+        return self._simulator
+
+    @simulator.setter
+    def simulator(self, simulator):
+        """Get the simulator this aircraft model works for."""
+        self._simulator = simulator
+
+    def doesHandle(self, aircraft, aircraftInfo):
+        """Determine if the model handles the given aircraft name.
+
+        This default implementation returns False."""
+        return False
+
+    def _addDatarefWithIndexMember(self, dest, name, type, attrName = None):
+        """Add the given X-Plane dataref name and type to the given array and a
+        member attribute with the given name."""
+        dest.append((name, type))
+        if attrName is not None:
+            setattr(self, attrName, len(dest)-1)
+
+    def _addDataWithIndexMembers(self, dest, prefix, data):
+        """Add X-Plane dataref data to the given array and also corresponding
+        index member variables with the given prefix.
+
+        data is a list of triplets of the following items:
+        - the name of the data item. The index member variable will have a name
+        created by prepending the given prefix to this name.
+        - the X-Plane dataref name
+        - the dataref type
+
+        The latter two items will be appended to dest."""
+        for (name, datarefName, type) in data:
+            self._addDatarefWithIndexMember(dest, datarefName, type,
+                                            prefix + name)
+
+    def addMonitoringData(self, data, fsType):
+        """Add the model-specific monitoring data to the given array."""
+        self._addDataWithIndexMembers(data, "_monidx_",
+                                      AircraftModel.monitoringData)
+        if self.simulator.hasXFMC:
+            print "xplane.AircraftModel.addMonitoringData: adding XFMC status dataref"""
+            self._addDatarefWithIndexMember(data, "xfmc/Status", TYPE_INT,
+                                            attrName = "_monidx_xfmcStatus")
+
+    def getAircraftState(self, aircraft, timestamp, data):
+        """Get an aircraft state object for the given monitoring data."""
+        state = fs.AircraftState()
+
+        xfmcLNAVOn = self.simulator.hasXFMC and \
+                     (data[self._monidx_xfmcStatus]&0x03==0x03)
+
+        state.timestamp = timestamp
+
+        state.latitude = data[self._monidx_latitude]
+        state.longitude = data[self._monidx_longitude]
+        if state.longitude>180.0: state.longitude = 360.0 - state.longitude
+
+        state.paused = data[self._monidx_paused]!=0 or \
+            data[self._monidx_replay]!=0
+        state.trickMode = data[self._monidx_replay]!=0
+
+        state.overspeed = data[self._monidx_overspeed]!=0
+        state.stalled = data[self._monidx_stalled]!=0
+        state.onTheGround = data[self._monidx_onTheGround]!=0
+
+        state.zfw = data[self._monidx_emptyWeight] + \
+                    data[self._monidx_payloadWeight]
+        state.grossWeight = data[self._monidx_grossWeight]
+
+        state.heading = data[self._monidx_heading]
+
+        state.pitch = data[self._monidx_pitch]
+        state.bank = data[self._monidx_bank]
+
+        state.ias = data[self._monidx_ias]
+        state.mach = data[self._monidx_mach]
+        state.groundSpeed = data[self._monidx_groundSpeed] * _mps2knots
+        state.vs = data[self._monidx_vs]
+
+        state.radioAltitude = data[self._monidx_radioAltitude]/.3048
+        state.altitude = data[self._monidx_altitude]/.3048
+
+        state.gLoad = data[self._monidx_gLoad]
+
+        flapsControl = data[self._monidx_flapsControl]
+        flapsIndex = int(flapsControl * (len(self._flapsNotches)-1))
+        state.flapsSet = 0 if flapsIndex<1 else self._flapsNotches[flapsIndex]
+
+        state.flaps = self._flapsNotches[-1]*data[self._monidx_flapsLeft]
+
+        state.navLightsOn = data[self._monidx_navLights] != 0
+        state.antiCollisionLightsOn = data[self._monidx_beaconLights] != 0
+        state.landingLightsOn = data[self._monidx_landingLights] != 0
+        state.strobeLightsOn = data[self._monidx_strobeLights] != 0
+
+        state.pitotHeatOn = data[self._monidx_pitot]!=0
+
+        state.parking = data[self._monidx_parking]>=0.5
+
+        state.gearControlDown = data[self._monidx_gearControl]!=0
+        state.gearsDown = data[self._monidx_noseGear][0]>0.99
+
+        state.spoilersArmed = None
+
+        state.spoilersExtension = data[self._monidx_spoilers]*100.0
+
+        state.altimeter = data[self._monidx_altimeter]* _hgin2hpa
+        state.altimeterReliable = True
+        state.qnh = data[self._monidx_qnh]/100.0
+
+        state.ils = None
+        state.ils_obs = None
+        state.ils_manual = False
+        state.nav1 = self._convertFrequency(data[self._monidx_nav1])
+        state.nav1_obs = self._convertOBS(data[self._monidx_nav1_obs])
+        state.nav1_manual = True
+        state.nav2 = self._convertFrequency(data[self._monidx_nav2])
+        state.nav2_obs = self._convertOBS(data[self._monidx_nav2_obs])
+        state.nav2_manual = not xfmcLNAVOn
+        state.adf1 = str(data[self._monidx_adf1])
+        state.adf2 = str(data[self._monidx_adf2])
+
+        state.squawk = "%04d" % (data[self._monidx_squawk],)
+
+        state.windSpeed = data[self._monidx_windSpeed]
+        state.windDirection = data[self._monidx_windDirection]
+        if state.windDirection<0.0: state.windDirection += 360.0
+
+        state.visibility = data[self._monidx_visibility]
+
+        state.cog = data[self._monidx_cog]
+
+        state.xpdrC = data[self._monidx_xpdrC]==2
+        state.autoXPDR = False
+
+        state.apMaster = data[self._monidx_apMaster]==2
+        apState = data[self._monidx_apState]
+        if xfmcLNAVOn:
+           state.apHeadingHold = None
+           state.apHeading = None
+        else:
+            state.apHeadingHold = (apState&0x00002)!=0
+            state.apHeading = data[self._monidx_apHeading]
+
+        state.apAltitudeHold = (apState&0x04000)!=0
+        state.apAltitude = data[self._monidx_apAltitude]
+
+        state.elevatorTrim = data[self._monidx_elevatorTrim] * 180.0 / math.pi
+
+        state.antiIceOn = data[self._monidx_antiIceOn]!=0 or \
+                          data[self._monidx_surfaceHeat]!=0 or \
+                          data[self._monidx_propHeat]!=0
+
+        return state
+
+#------------------------------------------------------------------------------
+
+class GenericAircraftModel(AircraftModel):
+    """A generic aircraft model that can handle the fuel levels, the N1 or RPM
+    values and some other common parameters in a generic way."""
+
+    def __init__(self, flapsNotches, fuelTanks, numEngines, isN1 = True):
+        """Construct the generic aircraft model with the given data.
+
+        flapsNotches is an array of how much degrees the individual flaps
+        notches mean.
+
+        fuelTanks is an array of const.FUELTANK_XXX constants about the
+        aircraft's fuel tanks. They will be converted to offsets.
+
+        numEngines is the number of engines the aircraft has.
+
+        isN1 determines if the engines have an N1 value or an RPM value
+        (e.g. pistons)."""
+        super(GenericAircraftModel, self).__init__(flapsNotches = flapsNotches)
+
+        self._fuelTanks = fuelTanks
+        self._fuelTankCapacities = [1.0] * len(fuelTanks)
+        self._fuelIndex = None
+        self._numEngines = numEngines
+        self._engineStartIndex = None
+        self._isN1 = isN1
+
+    def doesHandle(self, aircraft, aircraftInfo):
+        """Determine if the model handles the given aircraft name.
+
+        This implementation returns True."""
+        return True
+
+    def addMonitoringData(self, data, fsType):
+        """Add the model-specific monitoring data to the given array."""
+        super(GenericAircraftModel, self).addMonitoringData(data, fsType)
+
+        self._fuelIndex = self._addFuelData(data)
+
+        self._engineStartIndex = len(data)
+        if self._isN1:
+            self._addDatarefWithIndexMember(data,
+                                             "sim/flightmodel/engine/ENGN_N1_",
+                                             (TYPE_FLOAT_ARRAY,
+                                              self._numEngines))
+        else:
+            self._addDatarefWithIndexMember(data,
+                                            "sim/flightmodel/engine/POINT_tacrad",
+                                            (TYPE_FLOAT_ARRAY,
+                                             self._numEngines))
+
+        self._addDatarefWithIndexMember(data,
+                                        "sim/flightmodel/engine/ENGN_propmode",
+                                        (TYPE_INT_ARRAY, self._numEngines))
+
+    def getAircraftState(self, aircraft, timestamp, data):
+        """Get the aircraft state.
+
+        Get it from the parent, and then add the data about the fuel levels and
+        the engine parameters."""
+        state = super(GenericAircraftModel, self).getAircraftState(aircraft,
+                                                                   timestamp,
+                                                                   data)
+
+        state.fuel = []
+        state.totalFuel = 0.0
+
+        fuelAmounts = data[self._fuelIndex]
+        for i in range(0, len(self._fuelTanks)):
+            amount = fuelAmounts[i]
+            state.fuel.append((self._fuelTanks[i], amount))
+            state.totalFuel += amount
+
+        power = data[self._engineStartIndex]
+
+        state.n1 = power[:] if self._isN1 else None
+        state.rpm = None if self._isN1 else power[:]
+
+        propMode = data[self._engineStartIndex+1]
+        state.reverser = [mode == 3 for mode in propMode]
+
+        return state
+
+    def getFuel(self, handler, callback):
+        """Get the fuel information for this model.
+
+        See Simulator.getFuel for more information. This
+        implementation simply queries the fuel tanks given to the
+        constructor."""
+        data = []
+        self._addFuelData(data)
+        data.append( ("sim/aircraft/weight/acf_m_fuel_tot", TYPE_FLOAT) )
+        data.append( ("sim/aircraft/overflow/acf_tank_rat",
+                      (TYPE_FLOAT_ARRAY, len(self._fuelTanks)) ) )
+
+        handler.requestRead(data, self._handleFuelRetrieved,
+                            extra = callback)
+
+    def setFuelLevel(self, handler, levels):
+        """Set the fuel level.
+
+        See the description of Simulator.setFuelLevel. This
+        implementation simply sets the fuel tanks as given."""
+        data = []
+        for (tank, level) in levels:
+            try:
+                index = self._fuelTanks.index(tank)
+                data.append( ("sim/flightmodel/weight/m_fuel",
+                              (TYPE_FLOAT_ARRAY, 1, index),
+                              [level * self._fuelTankCapacities[index]]) )
+            except:
+                print "xplane.Simulator.setFuelLevel: invalid tank constant: %d" % \
+                  (tank,)
+
+        handler.requestWrite(data, self._handleFuelWritten)
+
+    def _addFuelData(self, data):
+        """Add the fuel offsets to the given data array.
+
+        Returns the index of the first fuel tank's data."""
+        fuelStartIndex = len(data)
+        data.append( ("sim/flightmodel/weight/m_fuel",
+                      (TYPE_FLOAT_ARRAY, len(self._fuelTanks) ) ) )
+
+        return fuelStartIndex
+
+    def _convertFuelData(self, data, index = 0, addCapacities = False):
+        """Convert the given data into a fuel info list.
+
+        The list consists of two or three-tuples of the following
+        items:
+        - the fuel tank ID,
+        - the amount of the fuel in kg,
+        - if addCapacities is True, the total capacity of the tank."""
+        fuelWeight = data[index] / 256.0
+        index += 1
+
+        result = []
+        totalFuel = 0
+        for fuelTank in self._fuelTanks:
+            capacity = data[index+1] * fuelWeight * const.LBSTOKG
+            if capacity>=1.0:
+                amount = data[index] * capacity / 128.0 / 65536.0
+
+                result.append( (fuelTank, amount, capacity) if addCapacities
+                               else (fuelTank, amount))
+                totalFuel += amount
+            index += 2
+
+        return (result, totalFuel)
+
+    def _handleFuelRetrieved(self, data, callback):
+        """Callback for a fuel retrieval request."""
+        result = []
+        totalCapacity = data[1]
+        for index in range(0, len(self._fuelTanks)):
+            amount = data[0][index]
+            capacity = data[2][index] * totalCapacity
+            self._fuelTankCapacities[index] = capacity
+            result.append( (self._fuelTanks[index], amount, capacity) )
+
+        callback(result)
+
+    def _handleFuelWritten(self, success, extra):
+        """Callback for a fuel setting request."""
+        pass
+
+#------------------------------------------------------------------------------
+
+class GenericModel(GenericAircraftModel):
+    """Generic aircraft model for an unknown type."""
+    def __init__(self):
+        """Construct the model."""
+        super(GenericModel, self). \
+            __init__(flapsNotches = [0, 10, 20, 30],
+                     fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT],
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/Generic"
+
+#------------------------------------------------------------------------------
+
+class B737Model(GenericAircraftModel):
+    """Generic model for the Boeing 737 Classing and NG aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
+
+    def __init__(self):
+        """Construct the model."""
+        super(B737Model, self). \
+            __init__(flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40],
+                     fuelTanks = B737Model.fuelTanks,
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/Generic Boeing 737"
+
+#------------------------------------------------------------------------------
+
+class B767Model(GenericAircraftModel):
+    """Generic model for the Boeing 767 aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
+
+    def __init__(self):
+        """Construct the model."""
+        super(B767Model, self). \
+            __init__(flapsNotches = [0, 1, 5, 15, 20, 25, 30],
+                     fuelTanks = B767Model.fuelTanks,
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/Generic Boeing 767"
+
+#------------------------------------------------------------------------------
+
+class DH8DModel(GenericAircraftModel):
+    """Generic model for the Bombardier  Dash 8-Q400 aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
+
+    def __init__(self):
+        """Construct the model."""
+        super(DH8DModel, self). \
+            __init__(flapsNotches = [0, 5, 10, 15, 35],
+                     fuelTanks = DH8DModel.fuelTanks,
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/Generic Bombardier Dash 8-Q400"
+
+#------------------------------------------------------------------------------
+
+class FJSDH8DModel(DH8DModel):
+    """Model handler for the FlyJSim Dash 8-Q400."""
+    @staticmethod
+    def doesHandle(aircraft, (tailnum, author, description, notes,
+                              icao, liveryPath)):
+        """Determine if this model handler handles the aircraft with the given
+        name."""
+        return aircraft.type==const.AIRCRAFT_DH8D and \
+          description.find("Dash 8 Q400")!=-1 and \
+          ((author in ["2012", "2013"] and tailnum=="N62890") or \
+           author.find("Jack Skieczius")!=-1)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/FlyJSim Bombardier Dash 8-Q400"
+
+    def getAircraftState(self, aircraft, timestamp, data):
+        """Get the aircraft state.
+
+        Get it from the parent, and then invert the pitot heat state."""
+        state = super(FJSDH8DModel, self).getAircraftState(aircraft,
+                                                           timestamp,
+                                                           data)
+        state.antiCollisionLightsOn = \
+          state.antiCollisionLightsOn or state.strobeLightsOn
+        state.cog = (state.cog / 0.0254 + 21.504) / 94.512
+
+        return state
+
+#------------------------------------------------------------------------------
+
+class CRJ2Model(GenericAircraftModel):
+    """Generic model for the Bombardier CRJ-200 aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
+
+    def __init__(self):
+        """Construct the model."""
+        super(CRJ2Model, self). \
+            __init__(flapsNotches = [0, 8, 20, 30, 45],
+                     fuelTanks = CRJ2Model.fuelTanks,
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/Generic Bombardier CRJ-200"
+
+#------------------------------------------------------------------------------
+
+class F70Model(GenericAircraftModel):
+    """Generic model for the Fokker F70 aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
+
+    def __init__(self):
+        """Construct the model."""
+        super(F70Model, self). \
+            __init__(flapsNotches = [0, 8, 15, 25, 42],
+                     fuelTanks = F70Model.fuelTanks,
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/Generic Fokker 70"
+
+#------------------------------------------------------------------------------
+
+class DC3Model(GenericAircraftModel):
+    """Generic model for the Lisunov Li-2 (DC-3) aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE,
+                 const.FUELTANK_RIGHT]
+    # fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
+    #              const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
+
+    def __init__(self):
+        """Construct the model."""
+        super(DC3Model, self). \
+            __init__(flapsNotches = [0, 15, 30, 45],
+                     fuelTanks = DC3Model.fuelTanks,
+                     numEngines = 2, isN1 = False)
+        self._leftLevel = 0.0
+        self._rightLevel = 0.0
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/Generic Lisunov Li-2 (DC-3)"
+
+#------------------------------------------------------------------------------
+
+class T134Model(GenericAircraftModel):
+    """Generic model for the Tupolev Tu-134 aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT_TIP, const.FUELTANK_EXTERNAL1,
+                 const.FUELTANK_LEFT_AUX,
+                 const.FUELTANK_CENTRE,
+                 const.FUELTANK_RIGHT_AUX,
+                 const.FUELTANK_EXTERNAL2, const.FUELTANK_RIGHT_TIP]
+
+    def __init__(self):
+        """Construct the model."""
+        super(T134Model, self). \
+            __init__(flapsNotches = [0, 10, 20, 30],
+                     fuelTanks = T134Model.fuelTanks,
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/Generic Tupolev Tu-134"
+
+#------------------------------------------------------------------------------
+
+class T154Model(GenericAircraftModel):
+    """Generic model for the Tupolev Tu-134 aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
+                 const.FUELTANK_CENTRE, const.FUELTANK_CENTRE2,
+                 const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
+
+    def __init__(self):
+        """Construct the model."""
+        super(T154Model, self). \
+            __init__(flapsNotches = [0, 15, 28, 45],
+                     fuelTanks = T154Model.fuelTanks,
+                     numEngines = 3)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/Generic Tupolev Tu-154"
+
+    def getAircraftState(self, aircraft, timestamp, data):
+        """Get an aircraft state object for the given monitoring data.
+
+        This removes the reverser value for the middle engine."""
+        state = super(T154Model, self).getAircraftState(aircraft, timestamp, data)
+        del state.reverser[1]
+        return state
+
+#------------------------------------------------------------------------------
+
+class YK40Model(GenericAircraftModel):
+    """Generic model for the Yakovlev Yak-40 aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
+
+    def __init__(self):
+        """Construct the model."""
+        super(YK40Model, self). \
+            __init__(flapsNotches = [0, 20, 35],
+                     fuelTanks = YK40Model.fuelTanks,
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/Generic Yakovlev Yak-40"
+
+#------------------------------------------------------------------------------
+
+_genericModels = { const.AIRCRAFT_B736  : B737Model,
+                   const.AIRCRAFT_B737  : B737Model,
+                   const.AIRCRAFT_B738  : B737Model,
+                   const.AIRCRAFT_B738C : B737Model,
+                   const.AIRCRAFT_B733  : B737Model,
+                   const.AIRCRAFT_B734  : B737Model,
+                   const.AIRCRAFT_B735  : B737Model,
+                   const.AIRCRAFT_DH8D  : DH8DModel,
+                   const.AIRCRAFT_B762  : B767Model,
+                   const.AIRCRAFT_B763  : B767Model,
+                   const.AIRCRAFT_CRJ2  : CRJ2Model,
+                   const.AIRCRAFT_F70   : F70Model,
+                   const.AIRCRAFT_DC3   : DC3Model,
+                   const.AIRCRAFT_T134  : T134Model,
+                   const.AIRCRAFT_T154  : T154Model,
+                   const.AIRCRAFT_YK40  : YK40Model }
+
+#------------------------------------------------------------------------------
+
+AircraftModel.registerSpecial(FJSDH8DModel)
