Index: misc/client.py
===================================================================
--- misc/client.py	(revision 39)
+++ misc/client.py	(revision 45)
@@ -443,7 +443,49 @@
             print >> sys.stderr, "Error code:", result
 
+    def do_register_hotkeys(self, args):
+        words = self._splitArgs(args)
+        if not words:
+            print >> sys.stderr, "Missing parameters"
+            return False
+
+        self._writeU8(0x51)
+        self._writeU32(len(words))
+        for word in words:
+            self._writeU16(int(word, 16))
+        self._flush()
+
+        result = self._readU8()
+        if result!=0:
+            print >> sys.stderr, "Error code:", result
+
+    def do_query_hotkeys(self, args):
+        self._writeU8(0x52)
+        self._flush()
+
+        result = self._readU8()
+        if result==0:
+            length = self._readU32()
+            values = []
+            for i in range(0, length):
+                values.append(self._readU8())
+            print values
+        else:
+            print >> sys.stderr, "Error code:", result
+
+    def do_unregister_hotkeys(self, args):
+        self._writeU8(0x53)
+        self._flush()
+
+        result = self._readU8()
+        if result!=0:
+            print >> sys.stderr, "Error code:", result
+
     def _writeU8(self, x):
         """Write the given value as an unsigned, 8-bit value."""
         self._stream.write(struct.pack("B", x))
+
+    def _writeU16(self, x):
+        """Write the given value as an unsigned, 16-bit value."""
+        self._stream.write(struct.pack("H", x))
 
     def _writeS32(self, x):
Index: src/plugin/src/xplra/Globals.h
===================================================================
--- src/plugin/src/xplra/Globals.h	(revision 44)
+++ src/plugin/src/xplra/Globals.h	(revision 45)
@@ -33,4 +33,5 @@
 #include "MessageWindow.h"
 #include "Menu.h"
+#include "HotkeyHandler.h"
 
 //------------------------------------------------------------------------------
@@ -67,4 +68,9 @@
     Menu menu;
 
+    /**
+     * The hotkey handler.
+     */
+    HotkeyHandler hotkeyHandler;
+
 public:
     /**
@@ -82,8 +88,21 @@
      */
     MessageWindow& getMessageWindow();
+
+    /**
+     * Get the hotkey handler.
+     */
+    HotkeyHandler& getHotkeyHandler();
 };
 
 //------------------------------------------------------------------------------
 // Inline definitions
+//------------------------------------------------------------------------------
+
+inline void Globals::getVersions(int& xplaneV, int& xplmV) const
+{
+    xplaneV = xplaneVersion;
+    xplmV = xplmVersion;
+}
+
 //------------------------------------------------------------------------------
 
@@ -95,8 +114,7 @@
 //------------------------------------------------------------------------------
 
-inline void Globals::getVersions(int& xplaneV, int& xplmV) const
+inline HotkeyHandler& Globals::getHotkeyHandler()
 {
-    xplaneV = xplaneVersion;
-    xplmV = xplmVersion;
+    return hotkeyHandler;
 }
 
Index: src/plugin/src/xplra/HotkeyHandler.cc
===================================================================
--- src/plugin/src/xplra/HotkeyHandler.cc	(revision 45)
+++ src/plugin/src/xplra/HotkeyHandler.cc	(revision 45)
@@ -0,0 +1,170 @@
+// Copyright (c) 2013 by István Váradi
+
+// This file is part of XPLRA, a remote-access plugin for X-Plane
+
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// The views and conclusions contained in the software and documentation are those
+// of the authors and should not be interpreted as representing official policies,
+// either expressed or implied, of the FreeBSD Project.
+
+//------------------------------------------------------------------------------
+
+#include "HotkeyHandler.h"
+
+#include "Protocol.h"
+
+#include <hu/varadiistvan/xplcommon/Util.h>
+
+#include <hu/varadiistvan/scpl/io/DataStream.h>
+
+#include <XPLMDisplay.h>
+
+//------------------------------------------------------------------------------
+
+using xplra::HotkeyHandler;
+
+using hu::varadiistvan::xplcommon::Util;
+
+using hu::varadiistvan::scpl::io::DataStream;
+
+//------------------------------------------------------------------------------
+
+HotkeyHandler::Hotkeys::Hotkeys(size_t numHotkeys, DataStream& stream) :
+    numHotkeys(numHotkeys),
+    hotkeys(new uint16_t[numHotkeys]),
+    pressed(0)
+{
+    if (stream.read(hotkeys, sizeof(uint16_t)*numHotkeys)) {
+        pressed = new uint8_t[numHotkeys];
+        memset(pressed, 0, sizeof(uint8_t)*numHotkeys);
+    } else {
+        delete hotkeys; hotkeys = 0;
+    }
+}
+
+//------------------------------------------------------------------------------
+
+HotkeyHandler::Hotkeys::~Hotkeys()
+{
+    delete [] pressed;
+    delete [] hotkeys;
+}
+
+//------------------------------------------------------------------------------
+
+bool HotkeyHandler::Hotkeys::handleKey(uint16_t code)
+{
+    for(size_t i = 0; i<numHotkeys; ++i) {
+        Util::debug("handleKey: %04x\n", hotkeys[i]);
+
+        if (hotkeys[i]==code) {
+            pressed[i] = 1;
+            return true;
+        }
+    }
+    return false;
+}
+
+//------------------------------------------------------------------------------
+
+void HotkeyHandler::Hotkeys::writePressed(DataStream& stream)
+{
+    uint8_t pressed1[numHotkeys];
+
+    for(size_t i = 0; i<numHotkeys; ++i) {
+        pressed1[i] = __sync_val_compare_and_swap(pressed + i, 1, 0);
+    }
+
+    stream.writeU32(numHotkeys);
+    stream.write(pressed1, sizeof(pressed1));
+}
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+
+int HotkeyHandler::keyCallback(char c, XPLMKeyFlags flags, char virtualKey,
+                               void* refCon)
+{
+    HotkeyHandler* hotkeyHandler = reinterpret_cast<HotkeyHandler*>(refCon);
+    return hotkeyHandler->handleKey(c, flags, virtualKey) ? 0 : 1;
+}
+
+//------------------------------------------------------------------------------
+
+HotkeyHandler::HotkeyHandler()
+{
+    XPLMRegisterKeySniffer(&keyCallback, 0, this);
+}
+
+//------------------------------------------------------------------------------
+
+void HotkeyHandler::registerHotkeys(Hotkeys* hotkeys)
+{
+    mutex.lock();
+    hotkeySets.push_back(hotkeys);
+    mutex.unlock();
+}
+
+//------------------------------------------------------------------------------
+
+void HotkeyHandler::unregisterHotkeys(Hotkeys* hotkeys)
+{
+    mutex.lock();
+    hotkeySets.remove(hotkeys);
+    mutex.unlock();
+}
+
+//------------------------------------------------------------------------------
+
+bool HotkeyHandler::handleKey(char /*c*/, XPLMKeyFlags flags, char virtualKey)
+{
+    if ( (flags&xplm_DownFlag)==0 ) return false;
+
+    uint16_t code = static_cast<uint16_t>(virtualKey);
+    if ( (flags&xplm_ShiftFlag)!=0 ) code |= Protocol::HOTKEY_MODIFIER_SHIFT;
+    if ( (flags&xplm_ControlFlag)!=0 ) code |= Protocol::HOTKEY_MODIFIER_CONTROL;
+
+    Util::debug("code=0x%04x\n", code);
+
+    bool handled = false;
+
+    mutex.lock();
+    for(hotkeySets_t::iterator i = hotkeySets.begin(); i!=hotkeySets.end();
+        ++i)
+    {
+        Hotkeys* hotkeys = *i;
+        if (hotkeys->handleKey(code)) {
+            handled = true;
+        }
+    }
+    mutex.unlock();
+
+    return handled;
+}
+
+//------------------------------------------------------------------------------
+
+// Local Variables:
+// mode: C++
+// c-basic-offset: 4
+// indent-tabs-mode: nil
+// End:
Index: src/plugin/src/xplra/HotkeyHandler.h
===================================================================
--- src/plugin/src/xplra/HotkeyHandler.h	(revision 45)
+++ src/plugin/src/xplra/HotkeyHandler.h	(revision 45)
@@ -0,0 +1,185 @@
+// Copyright (c) 2013 by István Váradi
+
+// This file is part of XPLRA, a remote-access plugin for X-Plane
+
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// The views and conclusions contained in the software and documentation are those
+// of the authors and should not be interpreted as representing official policies,
+// either expressed or implied, of the FreeBSD Project.
+
+#ifndef XPLRA_HOTKEYHANDLER_H
+#define XPLRA_HOTKEYHANDLER_H
+//------------------------------------------------------------------------------
+
+#include <hu/varadiistvan/scpl/Mutex.h>
+
+#include <XPLMDefs.h>
+
+#include <list>
+
+#include <inttypes.h>
+
+//------------------------------------------------------------------------------
+
+namespace hu { namespace varadiistvan { namespace scpl { namespace io {
+
+class DataStream;
+
+} } } }
+
+//------------------------------------------------------------------------------
+
+namespace xplra {
+
+//------------------------------------------------------------------------------
+
+/**
+ * The handler for hotkeys.
+ */
+class HotkeyHandler
+{
+public:
+    /**
+     * A set of hotkeys for a client.
+     */
+    class Hotkeys
+    {
+    private:
+        /**
+         * The number of hotkeys.
+         */
+        size_t numHotkeys;
+
+        /**
+         * The hotkey codes.
+         */
+        uint16_t* hotkeys;
+
+        /**
+         * The array of indicators whether the hotkey at the given
+         * index in the array of codes has been pressed.
+         */
+        uint8_t* pressed;
+
+    public:
+        /**
+         * Construct the set of hotkeys by reading the data from the
+         * given data stream.
+         */
+        Hotkeys(size_t numHotkeys, hu::varadiistvan::scpl::io::DataStream& stream);
+
+        /**
+         * Destroy the set of hotkeys.
+         */
+        ~Hotkeys();
+
+        /**
+         * Determine if this hotkey set is valid.
+         */
+        bool isValid() const;
+
+        /**
+         * Handle the hotkey with the given code. If it matches any of
+         * the codes registered, the corresponding value in the
+         * pressed array will be set atomically.
+         *
+         * @return whether the hotkey was handled or not.
+         */
+        bool handleKey(uint16_t code);
+
+        /**
+         * Write the pressed indicators into the given stream. It also
+         * clears the indicators.
+         */
+        void writePressed(hu::varadiistvan::scpl::io::DataStream& stream);
+    };
+
+    friend class Hotkeys;
+
+private:
+    /**
+     * Type for the the list of hotkey sets.
+     */
+    typedef std::list<Hotkeys*> hotkeySets_t;
+
+    /**
+     * The callback function receiving the keypresses.
+     */
+    static int keyCallback(char c, XPLMKeyFlags flags, char virtualKey,
+                           void* refCon);
+
+    /**
+     * Mutex to protect some common data structures.
+     */
+    hu::varadiistvan::scpl::Mutex mutex;
+
+    /**
+     * The list of hotkey sets.
+     */
+    hotkeySets_t hotkeySets;
+
+public:
+    /**
+     * Construct the hotkey handler.
+     */
+    HotkeyHandler();
+
+    /**
+     * Register the given set of hotkeys.
+     */
+    void registerHotkeys(Hotkeys* hotkeys);
+
+    /**
+     * Unregister the given set of hotkeys.
+     */
+    void unregisterHotkeys(Hotkeys* hotkeys);
+
+protected:
+    /**
+     * Handle the given hotkey code.
+     *
+     * @return true if the hotkey was handled, false otherwise.
+     */
+    bool handleKey(char c, XPLMKeyFlags flags, char virtualKey);
+};
+
+//------------------------------------------------------------------------------
+// Inline definitions
+//------------------------------------------------------------------------------
+
+inline bool HotkeyHandler::Hotkeys::isValid() const
+{
+    return hotkeys!=0;
+}
+
+//------------------------------------------------------------------------------
+
+} /* namespace xplra */
+
+//------------------------------------------------------------------------------
+#endif // XPLRA_HOTKEYHANDLER_H
+
+// Local Variables:
+// mode: C++
+// c-basic-offset: 4
+// indent-tabs-mode: nil
+// End:
Index: src/plugin/src/xplra/Makefile.am
===================================================================
--- src/plugin/src/xplra/Makefile.am	(revision 44)
+++ src/plugin/src/xplra/Makefile.am	(revision 45)
@@ -21,5 +21,6 @@
 	MessageWindow.cc		\
 	MessageRequest.cc		\
-	Menu.cc
+	Menu.cc				\
+	HotkeyHandler.cc
 
 noinst_HEADERS= \
@@ -41,5 +42,6 @@
 	MessageWindow.h			\
 	MessageRequest.h		\
-	Menu.h
+	Menu.h				\
+	HotkeyHandler.h
 
 if TARGET_API_POSIX
Index: src/plugin/src/xplra/Protocol.h
===================================================================
--- src/plugin/src/xplra/Protocol.h	(revision 39)
+++ src/plugin/src/xplra/Protocol.h	(revision 45)
@@ -107,4 +107,45 @@
 
     /**
+     * Command: register a set of hotkeys for the client. Old hotkeys,
+     * if any, are forgotten.
+     *
+     * The command is followed by the following data:
+     * - The number of the hotkeys defined (U32).
+     * - The code of the hotkeys (U16*number of the hotkeys).
+     *   The lower byte is the same as the X-Plane virtual key code
+     *   (the same as the ASCII code for numbers and upper-case letters),
+     *   the upper one is a logical OR of the HOTKEY_MODIFIER_XXX
+     *   codes.
+     *
+     * The reply consists of a result code only. It may fail with an
+     * invalid length code if the number of hotkeys is too large.
+     */
+    static const uint8_t COMMAND_REGISTER_HOTKEYS = 0x51;
+
+    /**
+     * Command: query the hotkeys.
+     *
+     * The reply consists of a result code, followed by the following
+     * data, if the result code is RESULT_OK:
+     * - The number of hotkeys defined (U32).
+     * - An array of U8 values each being 0 or 1 depending on whether
+     *   the corresponding hotkey has been pressed since the last
+     *   query (or the registration, whichever is later). The value at
+     *   index i corresponds to the hotkey code at index i in the
+     *   array passed with COMMAND_REGISTER_HOTKEYS.
+     *
+     * If not hotkey has been registered, the number of hotkeys is
+     * returned as 0.
+     */
+    static const uint8_t COMMAND_QUERY_HOTKEYS = 0x52;
+
+    /**
+     * Command: unregister the previously registered hotkeys.
+     *
+     * The reply is a result code.
+     */
+    static const uint8_t COMMAND_UNREGISTER_HOTKEYS = 0x53;
+
+    /**
      * Data type: int
      */
@@ -185,4 +226,14 @@
      */
     static const uint8_t RESULT_OTHER_ERROR = 0xff;
+
+    /**
+     * Hotkey modifier: shift
+     */
+    static const uint16_t HOTKEY_MODIFIER_SHIFT = 0x0100;
+
+    /**
+     * Hotkey modifier: control
+     */
+    static const uint16_t HOTKEY_MODIFIER_CONTROL = 0x0200;
 
     /**
@@ -201,4 +252,10 @@
      */
     static const float MAX_MESSAGE_DURATION = 5*60;
+
+    /**
+     * The maximal number of hotkeys that can be registered for a
+     * client.
+     */
+    static const size_t MAX_HOTKEY_COUNT = 128;
 
     /**
Index: src/plugin/src/xplra/ServerThread.cc
===================================================================
--- src/plugin/src/xplra/ServerThread.cc	(revision 44)
+++ src/plugin/src/xplra/ServerThread.cc	(revision 45)
@@ -76,4 +76,17 @@
 //------------------------------------------------------------------------------
 
+inline bool ServerThread::destroyHotkeys()
+{
+    if (hotkeys!=0) {
+        listenThread.getGlobals().getHotkeyHandler().unregisterHotkeys(hotkeys);
+        delete hotkeys; hotkeys = 0;
+        return true;
+    } else {
+        return false;
+    }
+}
+
+//------------------------------------------------------------------------------
+
 ServerThread::ServerThread(ListenThread& listenThread,
                            RequestQueue& requestQueue, LocalAcceptor& acceptor) :
@@ -84,5 +97,6 @@
     stream(*bufferedStream),
     nextGetMultiRequestID(1),
-    nextSetMultiRequestID(1)
+    nextSetMultiRequestID(1),
+    hotkeys(0)
 {
     instancesMutex.lock();
@@ -159,4 +173,10 @@
         } else if (command==Protocol::COMMAND_SHOW_MESSAGE) {
             if (!handleShowMessage()) break;
+        } else if (command==Protocol::COMMAND_REGISTER_HOTKEYS) {
+            if (!handleRegisterHotkeys()) break;
+        } else if (command==Protocol::COMMAND_QUERY_HOTKEYS) {
+            if (!handleQueryHotkeys()) break;
+        } else if (command==Protocol::COMMAND_UNREGISTER_HOTKEYS) {
+            if (!handleUnregisterHotkeys()) break;
         } else {
             stream.writeU8(Protocol::RESULT_INVALID_COMMAND);
@@ -164,4 +184,6 @@
         stream.flush();
     }
+
+    destroyHotkeys();
 
     Util::debug("hu.varadiistvan.xplra.ServerThread[%p]::run: quitting\n", this);
@@ -446,4 +468,52 @@
 //------------------------------------------------------------------------------
 
+bool ServerThread::handleRegisterHotkeys()
+{
+    size_t numHotkeys = stream.readU32();
+    if (!stream) return false;
+
+    if (numHotkeys>Protocol::MAX_HOTKEY_COUNT) {
+        stream.writeU8(Protocol::RESULT_INVALID_LENGTH);
+    } else {
+        HotkeyHandler::Hotkeys* newHotkeys =
+            new HotkeyHandler::Hotkeys(numHotkeys, stream);
+        if (newHotkeys->isValid()) {
+            destroyHotkeys();
+            hotkeys = newHotkeys;
+            listenThread.getGlobals().getHotkeyHandler().registerHotkeys(hotkeys);
+            stream.writeU8(Protocol::RESULT_OK);
+        } else {
+            delete newHotkeys;
+            return false;
+        }
+    }
+
+    return true;
+}
+
+//------------------------------------------------------------------------------
+
+bool ServerThread::handleQueryHotkeys()
+{
+    stream.writeU8(Protocol::RESULT_OK);
+    if (hotkeys==0) {
+        stream.writeU32(0);
+    } else {
+        hotkeys->writePressed(stream);
+    }
+    return true;
+}
+
+//------------------------------------------------------------------------------
+
+bool ServerThread::handleUnregisterHotkeys()
+{
+    destroyHotkeys();
+    stream.writeU8(Protocol::RESULT_OK);
+    return true;
+}
+
+//------------------------------------------------------------------------------
+
 // Local Variables:
 // mode: C++
Index: src/plugin/src/xplra/ServerThread.h
===================================================================
--- src/plugin/src/xplra/ServerThread.h	(revision 39)
+++ src/plugin/src/xplra/ServerThread.h	(revision 45)
@@ -31,4 +31,6 @@
 //------------------------------------------------------------------------------
 
+#include "HotkeyHandler.h"
+
 #include <hu/varadiistvan/scpl/Thread.h>
 
@@ -141,4 +143,9 @@
     setMultiRequests_t setMultiRequests;
 
+    /**
+     * The set of hotkeys currently being handled.
+     */
+    HotkeyHandler::Hotkeys* hotkeys;
+
 public:
     /**
@@ -255,4 +262,36 @@
      */
     bool handleShowMessage();
+
+    /**
+     * Handle the COMMAND_REGISTER_HOTKEYS command.
+     *
+     * @return true if we can continue, false if the thread should
+     * quit.
+     */
+    bool handleRegisterHotkeys();
+
+    /**
+     * Handle the COMMAND_QUERY_HOTKEYS command.
+     *
+     * @return true if we can continue, false if the thread should
+     * quit.
+     */
+    bool handleQueryHotkeys();
+
+    /**
+     * Handle the COMMAND_UNREGISTER_HOTKEYS command.
+     *
+     * @return true if we can continue, false if the thread should
+     * quit.
+     */
+    bool handleUnregisterHotkeys();
+
+private:
+    /**
+     * Destroy the current set of hotkeys, if any.
+     *
+     * @return whether there were any hotkeys or not.
+     */
+    bool destroyHotkeys();
 };
 
