diff --git a/cmake/modules/FindconnexionClient.cmake b/cmake/modules/FindconnexionClient.cmake
new file mode 100644
index 0000000000..1d6d7d4514
--- /dev/null
+++ b/cmake/modules/FindconnexionClient.cmake
@@ -0,0 +1,38 @@
+#
+#  FindconnexionClient.cmake
+# 
+#  Once done this will define
+#
+#  3DCONNEXIONCLIENT_INCLUDE_DIRS
+# 
+#  Created on 10/06/2015 by Marcel Verhagen
+#  Copyright 2015 High Fidelity, Inc.
+# 
+#  Distributed under the Apache License, Version 2.0.
+#  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+# 
+
+# setup hints for 3DCONNEXIONCLIENT search
+include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
+hifi_library_search_hints("connexionclient")
+
+if (APPLE)
+	find_library(3DconnexionClient 3DconnexionClient)
+	if(EXISTS ${3DconnexionClient})
+		set(CONNEXIONCLIENT_FOUND true)
+		set(CONNEXIONCLIENT_INCLUDE_DIR ${3DconnexionClient})
+		set(CONNEXIONCLIENT_LIBRARY ${3DconnexionClient})
+		set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "-weak_framework 3DconnexionClient")
+		message(STATUS "Found 3Dconnexion")
+	        mark_as_advanced(CONNEXIONCLIENT_INCLUDE_DIR CONNEXIONCLIENT_LIBRARY)
+	endif()
+endif()
+
+if (WIN32)
+	find_path(CONNEXIONCLIENT_INCLUDE_DIRS I3dMouseParams.h PATH_SUFFIXES Inc HINTS ${CONNEXIONCLIENT_SEARCH_DIRS})
+
+	include(FindPackageHandleStandardArgs)
+	find_package_handle_standard_args(connexionClient DEFAULT_MSG CONNEXIONCLIENT_INCLUDE_DIRS)
+
+	mark_as_advanced(CONNEXIONCLIENT_INCLUDE_DIRS CONNEXIONCLIENT_SEARCH_DIRS)
+endif()
diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt
index 0c44ac801f..4ee3709f4a 100644
--- a/interface/CMakeLists.txt
+++ b/interface/CMakeLists.txt
@@ -2,7 +2,7 @@ set(TARGET_NAME interface)
 project(${TARGET_NAME})
 
 # set a default root dir for each of our optional externals if it was not passed
-set(OPTIONAL_EXTERNALS "Faceshift" "Sixense" "LeapMotion" "RtMidi" "SDL2" "RSSDK")
+set(OPTIONAL_EXTERNALS "Faceshift" "Sixense" "LeapMotion" "RtMidi" "SDL2" "RSSDK" "connexionClient")
 foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
   string(TOUPPER ${EXTERNAL} ${EXTERNAL}_UPPERCASE)
   if (NOT ${${EXTERNAL}_UPPERCASE}_ROOT_DIR)
diff --git a/interface/external/connexionclient/Inc/I3dMouseParams.h b/interface/external/connexionclient/Inc/I3dMouseParams.h
new file mode 100644
index 0000000000..f250efe74f
--- /dev/null
+++ b/interface/external/connexionclient/Inc/I3dMouseParams.h
@@ -0,0 +1,79 @@
+//
+//  3DConnexion.cpp
+//  hifi
+//
+//  Created by MarcelEdward Verhagen on 09-06-15.
+//  Copyright 2015 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+
+#ifndef I3D_MOUSE_PARAMS_H
+#define I3D_MOUSE_PARAMS_H
+
+// Parameters for the 3D mouse based on the SDK from 3Dconnexion
+
+class I3dMouseSensor {
+public:
+    enum Speed {
+       SPEED_LOW = 0,
+       SPEED_MID,
+       SPEED_HIGH
+    };
+
+    virtual bool IsPanZoom() const = 0;
+    virtual bool IsRotate() const  = 0;
+    virtual Speed GetSpeed() const  = 0;
+
+    virtual void SetPanZoom(bool isPanZoom) = 0;
+    virtual void SetRotate(bool isRotate) = 0;
+    virtual void SetSpeed(Speed speed) = 0;
+
+protected:
+    virtual ~I3dMouseSensor() {}
+};
+
+class I3dMouseNavigation {
+public:
+   enum Pivot {
+      PIVOT_MANUAL = 0,
+      PIVOT_AUTO,
+      PIVOT_AUTO_OVERRIDE
+   };
+
+   enum Navigation {
+      NAVIGATION_OBJECT_MODE = 0,
+      NAVIGATION_CAMERA_MODE,
+      NAVIGATION_FLY_MODE,
+      NAVIGATION_WALK_MODE,
+      NAVIGATION_HELICOPTER_MODE
+   };
+
+   enum PivotVisibility {
+      PIVOT_HIDE = 0,
+      PIVOT_SHOW,
+      PIVOT_SHOW_MOVING
+   };
+
+    virtual Navigation GetNavigationMode() const  = 0;
+    virtual Pivot GetPivotMode() const  = 0;
+    virtual PivotVisibility GetPivotVisibility() const = 0;
+    virtual bool IsLockHorizon() const = 0;
+
+    virtual void SetLockHorizon(bool bOn) = 0;
+    virtual void SetNavigationMode(Navigation navigation) = 0;
+    virtual void SetPivotMode(Pivot pivot) = 0;
+    virtual void SetPivotVisibility(PivotVisibility visibility) = 0;
+
+protected:
+    virtual ~I3dMouseNavigation(){}
+};
+
+class I3dMouseParam : public I3dMouseSensor, public I3dMouseNavigation {
+public:
+    virtual ~I3dMouseParam() {}
+};
+
+#endif
diff --git a/interface/external/connexionclient/readme.txt b/interface/external/connexionclient/readme.txt
new file mode 100644
index 0000000000..dd67a29449
--- /dev/null
+++ b/interface/external/connexionclient/readme.txt
@@ -0,0 +1,4 @@
+The mac version does not require any files here. 3D connexion should be installed from 
+http://www.3dconnexion.eu/service/drivers.html 
+
+For windows a header file is required Inc/I3dMouseParams.h
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index fa2a82ecb4..33d88923eb 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -115,6 +115,7 @@
 #include "devices/MIDIManager.h"
 #include "devices/OculusManager.h"
 #include "devices/TV3DManager.h"
+#include "devices/3Dconnexion.h"
 
 #include "scripting/AccountScriptingInterface.h"
 #include "scripting/AudioDeviceScriptingInterface.h"
@@ -639,6 +640,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
     connect(applicationUpdater.data(), &AutoUpdater::newVersionIsAvailable, dialogsManager.data(), &DialogsManager::showUpdateDialog);
     applicationUpdater->checkForUpdate();
 
+    // the 3Dconnexion device wants to be initiliazed after a window is displayed.
+    ConnexionClient::init();
+
     auto& packetReceiver = nodeList->getPacketReceiver();
     packetReceiver.registerListener(PacketType::DomainConnectionDenied, this, "handleDomainConnectionDeniedPacket");
 }
@@ -750,6 +754,7 @@ Application::~Application() {
 
     Leapmotion::destroy();
     RealSense::destroy();
+    ConnexionClient::destroy();
 
     qInstallMessageHandler(NULL); // NOTE: Do this as late as possible so we continue to get our log messages
 }
@@ -1480,6 +1485,7 @@ void Application::focusOutEvent(QFocusEvent* event) {
     _keyboardMouseDevice.focusOutEvent(event);
     SixenseManager::getInstance().focusOutEvent();
     SDL2Manager::getInstance()->focusOutEvent();
+    ConnexionData::getInstance().focusOutEvent();
 
     // synthesize events for keys currently pressed, since we may not get their release events
     foreach (int key, _keysPressed) {
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index 91ae6a4d02..c347fdac67 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -29,6 +29,7 @@
 #include "devices/Faceshift.h"
 #include "devices/RealSense.h"
 #include "devices/SixenseManager.h"
+#include "devices/3Dconnexion.h"
 #include "MainWindow.h"
 #include "scripting/MenuScriptingInterface.h"
 #if defined(Q_OS_MAC) || defined(Q_OS_WIN)
@@ -447,6 +448,11 @@ Menu::Menu() {
     addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderLookAtVectors, 0, false);
     addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderFocusIndicator, 0, false);
     addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowWhosLookingAtMe, 0, false);
+    addCheckableActionToQMenuAndActionHash(avatarDebugMenu,
+                                           MenuOption::Connexion,
+                                           0, false,
+                                           &ConnexionClient::getInstance(),
+                                           SLOT(toggleConnexion(bool)));
 
     MenuWrapper* handOptionsMenu = developerMenu->addMenu("Hands");
     addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlignForearmsWithWrists, 0, false);
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index bf0f89abb5..0edd93c5a6 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -161,6 +161,7 @@ namespace MenuOption {
     const QString CenterPlayerInView = "Center Player In View";
     const QString Chat = "Chat...";
     const QString Collisions = "Collisions";
+    const QString Connexion = "Activate 3D Connexion Devices"";
     const QString Console = "Console...";
     const QString ControlWithSpeech = "Control With Speech";
     const QString CopyAddress = "Copy Address to Clipboard";
diff --git a/interface/src/devices/3Dconnexion.cpp b/interface/src/devices/3Dconnexion.cpp
new file mode 100755
index 0000000000..111e2d5991
--- /dev/null
+++ b/interface/src/devices/3Dconnexion.cpp
@@ -0,0 +1,1013 @@
+//
+//  3DConnexion.cpp
+//  hifi
+//
+//  Created by MarcelEdward Verhagen on 09-06-15.
+//  Copyright 2015 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include "3Dconnexion.h"
+#include "UserActivityLogger.h"
+
+const float MAX_AXIS = 75.0f;  // max forward = 2x speed
+
+void ConnexionData::focusOutEvent() {
+    _axisStateMap.clear();
+    _buttonPressedMap.clear();
+};
+
+ConnexionData& ConnexionData::getInstance() {
+    static ConnexionData sharedInstance;
+    return sharedInstance;
+}
+
+ConnexionData::ConnexionData() {
+}
+
+void ConnexionData::handleAxisEvent() {
+    //qCWarning(interfaceapp) << "pos state x = " << cc_position.x << " y = " << cc_position.y << " z = " << cc_position.z << " Rotation x = " << cc_rotation.x << " y = " << cc_rotation.y << " z = " << cc_rotation.z;
+    _axisStateMap[makeInput(ROTATION_AXIS_Y_POS).getChannel()] = (cc_rotation.y > 0.0f) ? cc_rotation.y / MAX_AXIS : 0.0f;
+    _axisStateMap[makeInput(ROTATION_AXIS_Y_NEG).getChannel()] = (cc_rotation.y < 0.0f) ? -cc_rotation.y / MAX_AXIS : 0.0f;
+    _axisStateMap[makeInput(POSITION_AXIS_X_POS).getChannel()] = (cc_position.x > 0.0f) ? cc_position.x / MAX_AXIS : 0.0f;
+    _axisStateMap[makeInput(POSITION_AXIS_X_NEG).getChannel()] = (cc_position.x < 0.0f) ? -cc_position.x / MAX_AXIS : 0.0f;
+    _axisStateMap[makeInput(POSITION_AXIS_Y_POS).getChannel()] = (cc_position.y > 0.0f) ? cc_position.y / MAX_AXIS : 0.0f;
+    _axisStateMap[makeInput(POSITION_AXIS_Y_NEG).getChannel()] = (cc_position.y < 0.0f) ? -cc_position.y / MAX_AXIS : 0.0f;
+    _axisStateMap[makeInput(POSITION_AXIS_Z_POS).getChannel()] = (cc_position.z > 0.0f) ? cc_position.z / MAX_AXIS : 0.0f;
+    _axisStateMap[makeInput(POSITION_AXIS_Z_NEG).getChannel()] = (cc_position.z < 0.0f) ? -cc_position.z / MAX_AXIS : 0.0f;
+    _axisStateMap[makeInput(ROTATION_AXIS_X_POS).getChannel()] = (cc_rotation.x > 0.0f) ? cc_rotation.x / MAX_AXIS : 0.0f;
+    _axisStateMap[makeInput(ROTATION_AXIS_X_NEG).getChannel()] = (cc_rotation.x < 0.0f) ? -cc_rotation.x / MAX_AXIS : 0.0f;
+    _axisStateMap[makeInput(ROTATION_AXIS_Z_POS).getChannel()] = (cc_rotation.z > 0.0f) ? cc_rotation.z / MAX_AXIS : 0.0f;
+    _axisStateMap[makeInput(ROTATION_AXIS_Z_NEG).getChannel()] = (cc_rotation.z < 0.0f) ? -cc_rotation.z / MAX_AXIS : 0.0f;
+}
+
+void ConnexionData::setButton(int lastButtonState) {
+    _buttonPressedMap.clear();
+    _buttonPressedMap.insert(lastButtonState);
+}
+
+void ConnexionData::registerToUserInputMapper(UserInputMapper& mapper) {
+    // Grab the current free device ID
+    _deviceID = mapper.getFreeDeviceID();
+
+    auto proxy = UserInputMapper::DeviceProxy::Pointer(new UserInputMapper::DeviceProxy("ConnexionClient"));
+    proxy->getButton = [this](const UserInputMapper::Input& input, int timestamp) -> bool { return this->getButton(input.getChannel()); };
+    proxy->getAxis = [this](const UserInputMapper::Input& input, int timestamp) -> float { return this->getAxis(input.getChannel()); };
+    proxy->getAvailabeInputs = [this]() -> QVector<UserInputMapper::InputPair> {
+        QVector<UserInputMapper::InputPair> availableInputs;
+
+        availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_1), "Left button"));
+        availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_2), "Right button"));
+        availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_3), "Both buttons"));
+
+        availableInputs.append(UserInputMapper::InputPair(makeInput(POSITION_AXIS_Y_NEG), "Move backward"));
+        availableInputs.append(UserInputMapper::InputPair(makeInput(POSITION_AXIS_Y_POS), "Move forward"));
+        availableInputs.append(UserInputMapper::InputPair(makeInput(POSITION_AXIS_X_POS), "Move right"));
+        availableInputs.append(UserInputMapper::InputPair(makeInput(POSITION_AXIS_X_NEG), "Move Left"));
+        availableInputs.append(UserInputMapper::InputPair(makeInput(POSITION_AXIS_Z_POS), "Move up"));
+        availableInputs.append(UserInputMapper::InputPair(makeInput(POSITION_AXIS_Z_NEG), "Move down"));
+        availableInputs.append(UserInputMapper::InputPair(makeInput(ROTATION_AXIS_Y_NEG), "Rotate backward"));
+        availableInputs.append(UserInputMapper::InputPair(makeInput(ROTATION_AXIS_Y_POS), "Rotate forward"));
+        availableInputs.append(UserInputMapper::InputPair(makeInput(ROTATION_AXIS_X_POS), "Rotate right"));
+        availableInputs.append(UserInputMapper::InputPair(makeInput(ROTATION_AXIS_X_NEG), "Rotate left"));
+        availableInputs.append(UserInputMapper::InputPair(makeInput(ROTATION_AXIS_Z_POS), "Rotate up"));
+        availableInputs.append(UserInputMapper::InputPair(makeInput(ROTATION_AXIS_Z_NEG), "Rotate down"));
+
+        return availableInputs;
+    };
+    proxy->resetDeviceBindings = [this, &mapper]() -> bool {
+        mapper.removeAllInputChannelsForDevice(_deviceID);
+        this->assignDefaultInputMapping(mapper);
+        return true;
+    };
+    mapper.registerDevice(_deviceID, proxy);
+}
+
+void ConnexionData::assignDefaultInputMapping(UserInputMapper& mapper) {
+    const float JOYSTICK_MOVE_SPEED = 1.0f;
+    //const float DPAD_MOVE_SPEED = 0.5f;
+    const float JOYSTICK_YAW_SPEED = 0.5f;
+    const float JOYSTICK_PITCH_SPEED = 0.25f;
+    const float BOOM_SPEED = 0.1f;
+
+    // Y axes are flipped (up is negative)
+    // postion: Movement, strafing
+    mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(POSITION_AXIS_Y_NEG), JOYSTICK_MOVE_SPEED);
+    mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(POSITION_AXIS_Y_POS), JOYSTICK_MOVE_SPEED);
+    mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(POSITION_AXIS_X_POS), JOYSTICK_MOVE_SPEED);
+    mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(POSITION_AXIS_X_NEG), JOYSTICK_MOVE_SPEED);
+    mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(POSITION_AXIS_Z_NEG), JOYSTICK_MOVE_SPEED);
+    mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(POSITION_AXIS_Z_POS), JOYSTICK_MOVE_SPEED);
+
+    // Rotation: Camera orientation with button 1
+    mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(ROTATION_AXIS_Z_POS), JOYSTICK_YAW_SPEED);
+    mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(ROTATION_AXIS_Z_NEG), JOYSTICK_YAW_SPEED);
+    mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(ROTATION_AXIS_Y_NEG), JOYSTICK_PITCH_SPEED);
+    mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(ROTATION_AXIS_Y_POS), JOYSTICK_PITCH_SPEED);
+
+    // Button controls
+    // Zoom
+    mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(BUTTON_1), BOOM_SPEED);
+    mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(BUTTON_2), BOOM_SPEED);
+
+    // Zoom
+    //  mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(ROTATION_AXIS_Z_NEG), BOOM_SPEED);
+    //  mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(ROTATION_AXIS_Z_POS), BOOM_SPEED);
+
+}
+
+float ConnexionData::getButton(int channel) const {
+    if (!_buttonPressedMap.empty()) {
+        if (_buttonPressedMap.find(channel) != _buttonPressedMap.end()) {
+            return 1.0f;
+        } else {
+            return 0.0f;
+        }
+    }
+    return 0.0f;
+}
+
+float ConnexionData::getAxis(int channel) const {
+    auto axis = _axisStateMap.find(channel);
+    if (axis != _axisStateMap.end()) {
+        return (*axis).second;
+    } else {
+        return 0.0f;
+    }
+}
+
+UserInputMapper::Input ConnexionData::makeInput(ConnexionData::ButtonChannel button) {
+    return UserInputMapper::Input(_deviceID, button, UserInputMapper::ChannelType::BUTTON);
+}
+
+UserInputMapper::Input ConnexionData::makeInput(ConnexionData::PositionChannel axis) {
+    return UserInputMapper::Input(_deviceID, axis, UserInputMapper::ChannelType::AXIS);
+}
+
+void ConnexionData::update() {
+    // the update is done in the ConnexionClient class.
+    // for windows in the nativeEventFilter the inputmapper is connected or registed or removed when an 3Dconnnexion device is attached or deteched
+    // for osx the api will call DeviceAddedHandler or DeviceRemoveHandler when a 3Dconnexion device is attached or detached
+}
+
+ConnexionClient& ConnexionClient::getInstance() {
+    static ConnexionClient sharedInstance;
+    return sharedInstance;
+}
+
+#ifdef HAVE_CONNEXIONCLIENT
+
+#ifdef _WIN32
+
+static ConnexionClient* gMouseInput = 0;
+
+void ConnexionClient::toggleConnexion(bool shouldEnable)
+{
+	ConnexionData& connexiondata = ConnexionData::getInstance();
+	if (shouldEnable && connexiondata.getDeviceID() == 0) {
+        ConnexionClient::init();
+    }
+	if (!shouldEnable && connexiondata.getDeviceID() != 0) {
+        ConnexionClient::destroy();
+    }
+
+}
+
+void ConnexionClient::init() {
+    if (Menu::getInstance()->isOptionChecked(MenuOption::Connexion)) {
+		ConnexionClient& cclient = ConnexionClient::getInstance();
+		cclient.fLast3dmouseInputTime = 0;
+
+		cclient.InitializeRawInput(GetActiveWindow());
+
+		gMouseInput = &cclient;
+
+		QAbstractEventDispatcher::instance()->installNativeEventFilter(&cclient);
+    }
+}
+
+void ConnexionClient::destroy() {
+	ConnexionClient& cclient = ConnexionClient::getInstance();
+	QAbstractEventDispatcher::instance()->removeNativeEventFilter(&cclient);
+	ConnexionData& connexiondata = ConnexionData::getInstance();
+	int deviceid = connexiondata.getDeviceID();
+	connexiondata.setDeviceID(0);
+	Application::getUserInputMapper()->removeDevice(deviceid);
+}
+
+#define LOGITECH_VENDOR_ID 0x46d
+
+#ifndef RIDEV_DEVNOTIFY
+#define RIDEV_DEVNOTIFY 0x00002000
+#endif
+
+const int TRACE_RIDI_DEVICENAME = 0;
+const int TRACE_RIDI_DEVICEINFO = 0;
+
+#ifdef _WIN64
+typedef unsigned __int64 QWORD;
+#endif
+
+// object angular velocity per mouse tick 0.008 milliradians per second per count
+static const double k3dmouseAngularVelocity = 8.0e-6; // radians per second per count
+
+static const int kTimeToLive = 5;
+
+enum ConnexionPid {
+    CONNEXIONPID_SPACEPILOT = 0xc625,
+    CONNEXIONPID_SPACENAVIGATOR = 0xc626,
+    CONNEXIONPID_SPACEEXPLORER = 0xc627,
+    CONNEXIONPID_SPACENAVIGATORFORNOTEBOOKS = 0xc628,
+    CONNEXIONPID_SPACEPILOTPRO = 0xc629
+};
+
+// e3dmouse_virtual_key
+enum V3dk {
+    V3DK_INVALID = 0
+    , V3DK_MENU = 1, V3DK_FIT
+    , V3DK_TOP, V3DK_LEFT, V3DK_RIGHT, V3DK_FRONT, V3DK_BOTTOM, V3DK_BACK
+    , V3DK_CW, V3DK_CCW
+    , V3DK_ISO1, V3DK_ISO2
+    , V3DK_1, V3DK_2, V3DK_3, V3DK_4, V3DK_5, V3DK_6, V3DK_7, V3DK_8, V3DK_9, V3DK_10
+    , V3DK_ESC, V3DK_ALT, V3DK_SHIFT, V3DK_CTRL
+    , V3DK_ROTATE, V3DK_PANZOOM, V3DK_DOMINANT
+    , V3DK_PLUS, V3DK_MINUS
+};
+
+struct tag_VirtualKeys {
+    ConnexionPid pid;
+    size_t nKeys;
+    V3dk *vkeys;
+};
+
+// e3dmouse_virtual_key
+static const V3dk SpaceExplorerKeys[] = {
+    V3DK_INVALID     // there is no button 0
+    , V3DK_1, V3DK_2
+    , V3DK_TOP, V3DK_LEFT, V3DK_RIGHT, V3DK_FRONT
+    , V3DK_ESC, V3DK_ALT, V3DK_SHIFT, V3DK_CTRL
+    , V3DK_FIT, V3DK_MENU
+    , V3DK_PLUS, V3DK_MINUS
+    , V3DK_ROTATE
+};
+
+//e3dmouse_virtual_key
+static const V3dk SpacePilotKeys[] = {
+    V3DK_INVALID
+    , V3DK_1, V3DK_2, V3DK_3, V3DK_4, V3DK_5, V3DK_6
+    , V3DK_TOP, V3DK_LEFT, V3DK_RIGHT, V3DK_FRONT
+    , V3DK_ESC, V3DK_ALT, V3DK_SHIFT, V3DK_CTRL
+    , V3DK_FIT, V3DK_MENU
+    , V3DK_PLUS, V3DK_MINUS
+    , V3DK_DOMINANT, V3DK_ROTATE
+};
+
+static const struct tag_VirtualKeys _3dmouseVirtualKeys[] = {
+    CONNEXIONPID_SPACEPILOT
+    , sizeof(SpacePilotKeys) / sizeof(SpacePilotKeys[0])
+    , const_cast<V3dk *>(SpacePilotKeys),
+    CONNEXIONPID_SPACEEXPLORER
+    , sizeof(SpaceExplorerKeys) / sizeof(SpaceExplorerKeys[0])
+    , const_cast<V3dk *>(SpaceExplorerKeys)
+};
+
+// Converts a hid device keycode (button identifier) of a pre-2009 3Dconnexion USB device to the standard 3d mouse virtual key definition.
+// pid USB Product ID (PID) of 3D mouse device
+// hidKeyCode  Hid keycode as retrieved from a Raw Input packet
+// return The standard 3d mouse virtual key (button identifier) or zero if an error occurs.
+
+// Converts a hid device keycode (button identifier) of a pre-2009 3Dconnexion USB device
+// to the standard 3d mouse virtual key definition.
+unsigned short HidToVirtualKey(unsigned long pid, unsigned short hidKeyCode) {
+    unsigned short virtualkey = hidKeyCode;
+    for (size_t i = 0; i<sizeof(_3dmouseVirtualKeys) / sizeof(_3dmouseVirtualKeys[0]); ++i)
+    {
+        if (pid == _3dmouseVirtualKeys[i].pid)
+        {
+            if (hidKeyCode < _3dmouseVirtualKeys[i].nKeys) {
+                virtualkey = _3dmouseVirtualKeys[i].vkeys[hidKeyCode];
+            } else {
+                virtualkey = V3DK_INVALID;
+            }
+            break;
+        }
+    }
+    // Remaining devices are unchanged
+    return virtualkey;
+}
+
+bool ConnexionClient::RawInputEventFilter(void* msg, long* result) {
+    ConnexionData& connexiondata = ConnexionData::getInstance();
+    if (ConnexionClient::Is3dmouseAttached() && connexiondata.getDeviceID() == 0) {
+        connexiondata.registerToUserInputMapper(*Application::getUserInputMapper());
+        connexiondata.assignDefaultInputMapping(*Application::getUserInputMapper());
+        UserActivityLogger::getInstance().connectedDevice("controller", "3Dconnexion");
+    } else if (!ConnexionClient::Is3dmouseAttached() && connexiondata.getDeviceID() != 0) {
+		int deviceid = connexiondata.getDeviceID();
+		connexiondata.setDeviceID(0);
+		Application::getUserInputMapper()->removeDevice(deviceid);
+    }
+
+    if (!ConnexionClient::Is3dmouseAttached()) {
+        return false;
+    }
+
+    MSG* message = (MSG*)(msg);
+
+    if (message->message == WM_INPUT) {
+        HRAWINPUT hRawInput = reinterpret_cast<HRAWINPUT>(message->lParam);
+        gMouseInput->OnRawInput(RIM_INPUT, hRawInput);
+        if (result != 0)  {
+            result = 0;
+        }
+        return true;
+    }
+    return false;
+}
+
+ConnexionClient::ConnexionClient() {
+
+}
+
+ConnexionClient::~ConnexionClient() {
+	ConnexionClient& cclient = ConnexionClient::getInstance();
+	QAbstractEventDispatcher::instance()->removeNativeEventFilter(&cclient);
+}
+
+// Access the mouse parameters structure
+I3dMouseParam& ConnexionClient::MouseParams() {
+    return f3dMouseParams;
+}
+
+// Access the mouse parameters structure
+const I3dMouseParam& ConnexionClient::MouseParams() const {
+    return f3dMouseParams;
+}
+
+//Called with the processed motion data when a 3D mouse event is received
+void ConnexionClient::Move3d(HANDLE device, std::vector<float>& motionData) {
+    Q_UNUSED(device);
+    ConnexionData& connexiondata = ConnexionData::getInstance();
+    connexiondata.cc_position = { motionData[0] * 1000, motionData[1] * 1000, motionData[2] * 1000 };
+    connexiondata.cc_rotation = { motionData[3] * 1500, motionData[4] * 1500, motionData[5] * 1500 };
+    connexiondata.handleAxisEvent();
+}
+
+//Called when a 3D mouse key is pressed
+void ConnexionClient::On3dmouseKeyDown(HANDLE device, int virtualKeyCode) {
+    Q_UNUSED(device);
+    ConnexionData& connexiondata = ConnexionData::getInstance();
+    connexiondata.setButton(virtualKeyCode);
+}
+
+//Called when a 3D mouse key is released
+void ConnexionClient::On3dmouseKeyUp(HANDLE device, int virtualKeyCode) {
+    Q_UNUSED(device);
+    ConnexionData& connexiondata = ConnexionData::getInstance();
+    connexiondata.setButton(0);
+}
+
+//Get an initialized array of PRAWINPUTDEVICE for the 3D devices
+//pNumDevices returns the number of devices to register. Currently this is always 1.
+static PRAWINPUTDEVICE GetDevicesToRegister(unsigned int* pNumDevices) {
+    // Array of raw input devices to register
+    static RAWINPUTDEVICE sRawInputDevices[] = {
+        { 0x01, 0x08, 0x00, 0x00 } // Usage Page = 0x01 Generic Desktop Page, Usage Id= 0x08 Multi-axis Controller
+    };
+
+    if (pNumDevices) {
+        *pNumDevices = sizeof(sRawInputDevices) / sizeof(sRawInputDevices[0]);
+    }
+
+    return sRawInputDevices;
+}
+
+//Detect the 3D mouse
+bool ConnexionClient::Is3dmouseAttached() {
+    unsigned int numDevicesOfInterest = 0;
+    PRAWINPUTDEVICE devicesToRegister = GetDevicesToRegister(&numDevicesOfInterest);
+
+    unsigned int nDevices = 0;
+
+    if (::GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)) != 0) {
+        return false;
+    }
+
+    if (nDevices == 0) {
+        return false;
+    }
+
+    std::vector<RAWINPUTDEVICELIST> rawInputDeviceList(nDevices);
+    if (::GetRawInputDeviceList(&rawInputDeviceList[0], &nDevices, sizeof(RAWINPUTDEVICELIST)) == static_cast<unsigned int>(-1)) {
+        return false;
+    }
+
+    for (unsigned int i = 0; i < nDevices; ++i) {
+        RID_DEVICE_INFO rdi = { sizeof(rdi) };
+        unsigned int cbSize = sizeof(rdi);
+
+        if (GetRawInputDeviceInfo(rawInputDeviceList[i].hDevice, RIDI_DEVICEINFO, &rdi, &cbSize) > 0) {
+            //skip non HID and non logitec (3DConnexion) devices
+            if (rdi.dwType != RIM_TYPEHID || rdi.hid.dwVendorId != LOGITECH_VENDOR_ID) {
+                continue;
+            }
+
+            //check if devices matches Multi-axis Controller
+            for (unsigned int j = 0; j < numDevicesOfInterest; ++j) {
+                if (devicesToRegister[j].usUsage == rdi.hid.usUsage
+                    && devicesToRegister[j].usUsagePage == rdi.hid.usUsagePage) {
+                    return true;
+                }
+            }
+        }
+    }
+    return false;
+}
+
+// Initialize the window to recieve raw-input messages
+// This needs to be called initially so that Windows will send the messages from the 3D mouse to the window.
+bool ConnexionClient::InitializeRawInput(HWND hwndTarget) {
+    fWindow = hwndTarget;
+
+    // Simply fail if there is no window
+    if (!hwndTarget) {
+        return false;
+    }
+
+    unsigned int numDevices = 0;
+    PRAWINPUTDEVICE devicesToRegister = GetDevicesToRegister(&numDevices);
+
+    if (numDevices == 0) {
+        return false;
+    }
+
+    // Get OS version.
+    OSVERSIONINFO osvi = { sizeof(OSVERSIONINFO), 0 };
+    ::GetVersionEx(&osvi);
+
+    unsigned int cbSize = sizeof(devicesToRegister[0]);
+    for (size_t i = 0; i < numDevices; i++) {
+        // Set the target window to use
+        //devicesToRegister[i].hwndTarget = hwndTarget;
+
+        // If Vista or newer, enable receiving the WM_INPUT_DEVICE_CHANGE message.
+        if (osvi.dwMajorVersion >= 6) {
+            devicesToRegister[i].dwFlags |= RIDEV_DEVNOTIFY;
+        }
+    }
+    return (::RegisterRawInputDevices(devicesToRegister, numDevices, cbSize) != FALSE);
+}
+
+//Get the raw input data from Windows
+UINT ConnexionClient::GetRawInputBuffer(PRAWINPUT pData, PUINT pcbSize, UINT cbSizeHeader) {
+    //Includes workaround for incorrect alignment of the RAWINPUT structure on x64 os
+    //when running as Wow64 (copied directly from 3DConnexion code)
+#ifdef _WIN64
+    return ::GetRawInputBuffer(pData, pcbSize, cbSizeHeader);
+#else
+    BOOL bIsWow64 = FALSE;
+    ::IsWow64Process(GetCurrentProcess(), &bIsWow64);
+    if (!bIsWow64 || pData == NULL) {
+        return ::GetRawInputBuffer(pData, pcbSize, cbSizeHeader);
+    } else {
+        HWND hwndTarget = fWindow;
+
+        size_t cbDataSize = 0;
+        UINT nCount = 0;
+        PRAWINPUT pri = pData;
+
+        MSG msg;
+        while (PeekMessage(&msg, hwndTarget, WM_INPUT, WM_INPUT, PM_NOREMOVE)) {
+            HRAWINPUT hRawInput = reinterpret_cast<HRAWINPUT>(msg.lParam);
+            size_t cbSize = *pcbSize - cbDataSize;
+            if (::GetRawInputData(hRawInput, RID_INPUT, pri, &cbSize, cbSizeHeader) == static_cast<UINT>(-1)) {
+                if (nCount == 0) {
+                    return static_cast<UINT>(-1);
+                } else {
+                    break;
+                }
+            }
+            ++nCount;
+
+            // Remove the message for the data just read
+            PeekMessage(&msg, hwndTarget, WM_INPUT, WM_INPUT, PM_REMOVE);
+
+            pri = NEXTRAWINPUTBLOCK(pri);
+            cbDataSize = reinterpret_cast<ULONG_PTR>(pri)-reinterpret_cast<ULONG_PTR>(pData);
+            if (cbDataSize >= *pcbSize) {
+                cbDataSize = *pcbSize;
+                break;
+            }
+        }
+        return nCount;
+    }
+#endif
+}
+
+// Process the raw input device data
+// On3dmouseInput() does all the preprocessing of the rawinput device data before
+// finally calling the Move3d method.
+void ConnexionClient::On3dmouseInput() {
+    // Don't do any data processing in background
+    bool bIsForeground = (::GetActiveWindow() != NULL);
+    if (!bIsForeground) {
+        // set all cached data to zero so that a zero event is seen and the cached data deleted
+        for (std::map<HANDLE, TInputData>::iterator it = fDevice2Data.begin(); it != fDevice2Data.end(); it++) {
+            it->second.fAxes.assign(6, .0);
+            it->second.fIsDirty = true;
+        }
+    }
+
+    DWORD dwNow = ::GetTickCount();           // Current time;
+    DWORD dwElapsedTime;                      // Elapsed time since we were last here
+
+    if (0 == fLast3dmouseInputTime) {
+        dwElapsedTime = 10;                    // System timer resolution
+    } else {
+        dwElapsedTime = dwNow - fLast3dmouseInputTime;
+        if (fLast3dmouseInputTime > dwNow) {
+            dwElapsedTime = ~dwElapsedTime + 1;
+        }
+        if (dwElapsedTime<1) {
+            dwElapsedTime = 1;
+        } else if (dwElapsedTime > 500) {
+            // Check for wild numbers because the device was removed while sending data
+            dwElapsedTime = 10;
+        }
+    }
+
+    //qDebug("On3DmouseInput() period is %dms\n", dwElapsedTime);
+
+    float mouseData2Rotation = k3dmouseAngularVelocity;
+    // v = w * r,  we don't know r yet so lets assume r=1.)
+    float mouseData2PanZoom = k3dmouseAngularVelocity;
+
+    // Grab the I3dmouseParam interface
+    I3dMouseParam& i3dmouseParam = f3dMouseParams;
+    // Take a look at the users preferred speed setting and adjust the sensitivity accordingly
+    I3dMouseSensor::Speed speedSetting = i3dmouseParam.GetSpeed();
+    // See "Programming for the 3D Mouse", Section 5.1.3
+    float speed = (speedSetting == I3dMouseSensor::SPEED_LOW ? 0.25f : speedSetting == I3dMouseSensor::SPEED_HIGH ? 4.f : 1.f);
+
+    // Multiplying by the following will convert the 3d mouse data to real world units
+    mouseData2PanZoom *= speed;
+    mouseData2Rotation *= speed;
+
+    std::map<HANDLE, TInputData>::iterator iterator = fDevice2Data.begin();
+    while (iterator != fDevice2Data.end()) {
+
+        // If we have not received data for a while send a zero event
+        if ((--(iterator->second.fTimeToLive)) == 0) {
+            iterator->second.fAxes.assign(6, .0);
+        } else if ( !iterator->second.fIsDirty) { //!t_bPoll3dmouse &&
+            // If we are not polling then only handle the data that was actually received
+            ++iterator;
+            continue;
+        }
+        iterator->second.fIsDirty = false;
+
+        // get a copy of the device
+        HANDLE hdevice = iterator->first;
+
+        // get a copy of the motion vectors and apply the user filters
+        std::vector<float> motionData = iterator->second.fAxes;
+
+        // apply the user filters
+
+        // Pan Zoom filter
+        // See "Programming for the 3D Mouse", Section 5.1.2
+        if (!i3dmouseParam.IsPanZoom()) {
+        // Pan zoom is switched off so set the translation vector values to zero
+            motionData[0] = motionData[1] = motionData[2] = 0.;
+        }
+
+        // Rotate filter
+        // See "Programming for the 3D Mouse", Section 5.1.1
+        if (!i3dmouseParam.IsRotate()) {
+        // Rotate is switched off so set the rotation vector values to zero
+            motionData[3] = motionData[4] = motionData[5] = 0.;
+        }
+
+        // convert the translation vector into physical data
+        for (int axis = 0; axis < 3; axis++) {
+            motionData[axis] *= mouseData2PanZoom;
+        }
+
+        // convert the directed Rotate vector into physical data
+        // See "Programming for the 3D Mouse", Section 7.2.2
+        for (int axis = 3; axis < 6; axis++) {
+            motionData[axis] *= mouseData2Rotation;
+        }
+
+        // Now that the data has had the filters and sensitivty settings applied
+        // calculate the displacements since the last view update
+        for (int axis = 0; axis < 6; axis++) {
+            motionData[axis] *= dwElapsedTime;
+        }
+
+        // Now a bit of book keeping before passing on the data
+        if (iterator->second.IsZero()) {
+            iterator = fDevice2Data.erase(iterator);
+        } else {
+            ++iterator;
+        }
+
+        // Work out which will be the next device
+        HANDLE hNextDevice = 0;
+        if (iterator != fDevice2Data.end()) {
+            hNextDevice = iterator->first;
+        }
+
+        // Pass the 3dmouse input to the view controller
+        Move3d(hdevice, motionData);
+
+        // Because we don't know what happened in the previous call, the cache might have
+        // changed so reload the iterator
+        iterator = fDevice2Data.find(hNextDevice);
+    }
+
+    if (!fDevice2Data.empty()) {
+        fLast3dmouseInputTime = dwNow;
+    } else {
+        fLast3dmouseInputTime = 0;
+    }
+}
+
+//Called when new raw input data is available
+void ConnexionClient::OnRawInput(UINT nInputCode, HRAWINPUT hRawInput) {
+    const size_t cbSizeOfBuffer = 1024;
+    BYTE pBuffer[cbSizeOfBuffer];
+
+    PRAWINPUT pRawInput = reinterpret_cast<PRAWINPUT>(pBuffer);
+    UINT cbSize = cbSizeOfBuffer;
+
+    if (::GetRawInputData(hRawInput, RID_INPUT, pRawInput, &cbSize, sizeof(RAWINPUTHEADER)) == static_cast<UINT>(-1)) {
+        return;
+    }
+
+    bool b3dmouseInput = TranslateRawInputData(nInputCode, pRawInput);
+    ::DefRawInputProc(&pRawInput, 1, sizeof(RAWINPUTHEADER));
+
+    // Check for any buffered messages
+    cbSize = cbSizeOfBuffer;
+    UINT nCount = this->GetRawInputBuffer(pRawInput, &cbSize, sizeof(RAWINPUTHEADER));
+    if (nCount == (UINT)-1) {
+        qDebug("GetRawInputBuffer returned error %d\n", GetLastError());
+    }
+
+    while (nCount>0 && nCount != static_cast<UINT>(-1)) {
+        PRAWINPUT pri = pRawInput;
+        UINT nInput;
+        for (nInput = 0; nInput<nCount; ++nInput) {
+            b3dmouseInput |= TranslateRawInputData(nInputCode, pri);
+            // clean the buffer
+            ::DefRawInputProc(&pri, 1, sizeof(RAWINPUTHEADER));
+
+            pri = NEXTRAWINPUTBLOCK(pri);
+        }
+        cbSize = cbSizeOfBuffer;
+        nCount = this->GetRawInputBuffer(pRawInput, &cbSize, sizeof(RAWINPUTHEADER));
+    }
+
+    // If we have mouse input data for the app then tell tha app about it
+    if (b3dmouseInput) {
+        On3dmouseInput();
+    }
+}
+
+bool ConnexionClient::TranslateRawInputData(UINT nInputCode, PRAWINPUT pRawInput) {
+    bool bIsForeground = (nInputCode == RIM_INPUT);
+
+    //qDebug("Rawinput.header.dwType=0x%x\n", pRawInput->header.dwType);
+
+    // We are not interested in keyboard or mouse data received via raw input
+    if (pRawInput->header.dwType != RIM_TYPEHID) {
+        return false;
+    }
+
+    if (TRACE_RIDI_DEVICENAME == 1) {
+        UINT dwSize = 0;
+        if (::GetRawInputDeviceInfo(pRawInput->header.hDevice, RIDI_DEVICENAME, NULL, &dwSize) == 0)  {
+            std::vector<wchar_t> szDeviceName(dwSize + 1);
+            if (::GetRawInputDeviceInfo(pRawInput->header.hDevice, RIDI_DEVICENAME, &szDeviceName[0], &dwSize) >0) {
+                qDebug("Device Name = %s\nDevice handle = 0x%x\n", &szDeviceName[0], pRawInput->header.hDevice);
+            }
+        }
+    }
+
+    RID_DEVICE_INFO sRidDeviceInfo;
+    sRidDeviceInfo.cbSize = sizeof(RID_DEVICE_INFO);
+    UINT cbSize = sizeof(RID_DEVICE_INFO);
+
+    if (::GetRawInputDeviceInfo(pRawInput->header.hDevice, RIDI_DEVICEINFO, &sRidDeviceInfo, &cbSize) == cbSize) {
+        if (TRACE_RIDI_DEVICEINFO == 1) {
+            switch (sRidDeviceInfo.dwType)  {
+                case RIM_TYPEMOUSE:
+                    qDebug("\tsRidDeviceInfo.dwType=RIM_TYPEMOUSE\n");
+                    break;
+                case RIM_TYPEKEYBOARD:
+                    qDebug("\tsRidDeviceInfo.dwType=RIM_TYPEKEYBOARD\n");
+                    break;
+                case RIM_TYPEHID:
+                    qDebug("\tsRidDeviceInfo.dwType=RIM_TYPEHID\n");
+                    qDebug("\tVendor=0x%x\n\tProduct=0x%x\n\tUsagePage=0x%x\n\tUsage=0x%x\n",
+                           sRidDeviceInfo.hid.dwVendorId,
+                           sRidDeviceInfo.hid.dwProductId,
+                           sRidDeviceInfo.hid.usUsagePage,
+                           sRidDeviceInfo.hid.usUsage);
+                    break;
+            }
+        }
+
+        if (sRidDeviceInfo.hid.dwVendorId == LOGITECH_VENDOR_ID) {
+            if (pRawInput->data.hid.bRawData[0] == 0x01) { // Translation vector
+                TInputData& deviceData = fDevice2Data[pRawInput->header.hDevice];
+                deviceData.fTimeToLive = kTimeToLive;
+                if (bIsForeground) {
+                    short* pnRawData = reinterpret_cast<short*>(&pRawInput->data.hid.bRawData[1]);
+                    // Cache the pan zoom data
+                    deviceData.fAxes[0] = static_cast<float>(pnRawData[0]);
+                    deviceData.fAxes[1] = static_cast<float>(pnRawData[1]);
+                    deviceData.fAxes[2] = static_cast<float>(pnRawData[2]);
+
+                    //qDebug("Pan/Zoom RI Data =\t0x%x,\t0x%x,\t0x%x\n", pnRawData[0], pnRawData[1], pnRawData[2]);
+
+                    if (pRawInput->data.hid.dwSizeHid >= 13) { // Highspeed package
+                        // Cache the rotation data
+                        deviceData.fAxes[3] = static_cast<float>(pnRawData[3]);
+                        deviceData.fAxes[4] = static_cast<float>(pnRawData[4]);
+                        deviceData.fAxes[5] = static_cast<float>(pnRawData[5]);
+                        deviceData.fIsDirty = true;
+
+                        //qDebug("Rotation RI Data =\t0x%x,\t0x%x,\t0x%x\n", pnRawData[3], pnRawData[4], pnRawData[5]);
+                        return true;
+                    }
+                } else { // Zero out the data if the app is not in forground
+                    deviceData.fAxes.assign(6, 0.f);
+                }
+            } else if (pRawInput->data.hid.bRawData[0] == 0x02) { // Rotation vector
+                // If we are not in foreground do nothing
+                // The rotation vector was zeroed out with the translation vector in the previous message
+                if (bIsForeground) {
+                    TInputData& deviceData = fDevice2Data[pRawInput->header.hDevice];
+                    deviceData.fTimeToLive = kTimeToLive;
+
+                    short* pnRawData = reinterpret_cast<short*>(&pRawInput->data.hid.bRawData[1]);
+                    // Cache the rotation data
+                    deviceData.fAxes[3] = static_cast<float>(pnRawData[0]);
+                    deviceData.fAxes[4] = static_cast<float>(pnRawData[1]);
+                    deviceData.fAxes[5] = static_cast<float>(pnRawData[2]);
+                    deviceData.fIsDirty = true;
+
+                    //qDebug("Rotation RI Data =\t0x%x,\t0x%x,\t0x%x\n", pnRawData[0], pnRawData[1], pnRawData[2]);
+
+                    return true;
+                }
+            } else if (pRawInput->data.hid.bRawData[0] == 0x03) { // Keystate change
+                // this is a package that contains 3d mouse keystate information
+                // bit0=key1, bit=key2 etc.
+
+                unsigned long dwKeystate = *reinterpret_cast<unsigned long*>(&pRawInput->data.hid.bRawData[1]);
+
+                //qDebug("ButtonData =0x%x\n", dwKeystate);
+
+                // Log the keystate changes
+                unsigned long dwOldKeystate = fDevice2Keystate[pRawInput->header.hDevice];
+                if (dwKeystate != 0) {
+                    fDevice2Keystate[pRawInput->header.hDevice] = dwKeystate;
+                } else {
+                    fDevice2Keystate.erase(pRawInput->header.hDevice);
+                }
+
+                //  Only call the keystate change handlers if the app is in foreground
+                if (bIsForeground) {
+                    unsigned long dwChange = dwKeystate ^ dwOldKeystate;
+
+                    for (int nKeycode = 1; nKeycode<33; nKeycode++) {
+                        if (dwChange & 0x01) {
+                            int nVirtualKeyCode = HidToVirtualKey(sRidDeviceInfo.hid.dwProductId, nKeycode);
+                            if (nVirtualKeyCode) {
+                                if (dwKeystate & 0x01) {
+                                    On3dmouseKeyDown(pRawInput->header.hDevice, nVirtualKeyCode);
+                                } else {
+                                    On3dmouseKeyUp(pRawInput->header.hDevice, nVirtualKeyCode);
+                                }
+                            }
+                        }
+                        dwChange >>= 1;
+                        dwKeystate >>= 1;
+                    }
+                }
+            }
+        }
+    }
+    return false;
+}
+
+MouseParameters::MouseParameters() : fNavigation(NAVIGATION_OBJECT_MODE)
+                                    , fPivot(PIVOT_AUTO)
+                                    , fPivotVisibility(PIVOT_SHOW)
+                                    , fIsLockHorizon(true)
+                                    , fIsPanZoom(true)
+                                    , fIsRotate(true)
+                                    , fSpeed(SPEED_LOW) {
+}
+
+MouseParameters::~MouseParameters() {
+}
+
+bool MouseParameters::IsPanZoom()  const {
+    return fIsPanZoom;
+}
+
+bool MouseParameters::IsRotate()  const {
+    return fIsRotate;
+}
+
+MouseParameters::Speed MouseParameters::GetSpeed()  const {
+    return fSpeed;
+}
+
+void MouseParameters::SetPanZoom(bool isPanZoom) {
+    fIsPanZoom=isPanZoom;
+}
+
+void MouseParameters::SetRotate(bool isRotate) {
+    fIsRotate=isRotate;
+}
+
+void MouseParameters::SetSpeed(Speed speed) {
+    fSpeed=speed;
+}
+
+MouseParameters::Navigation MouseParameters::GetNavigationMode() const {
+    return fNavigation;
+}
+
+MouseParameters::Pivot MouseParameters::GetPivotMode() const {
+    return fPivot;
+}
+
+MouseParameters::PivotVisibility MouseParameters::GetPivotVisibility() const {
+    return fPivotVisibility;
+}
+
+bool MouseParameters::IsLockHorizon() const {
+    return fIsLockHorizon;
+}
+
+void MouseParameters::SetLockHorizon(bool bOn) {
+    fIsLockHorizon=bOn;
+}
+
+void MouseParameters::SetNavigationMode(Navigation navigation) {
+    fNavigation=navigation;
+}
+
+void MouseParameters::SetPivotMode(Pivot pivot) {
+    if (fPivot!=PIVOT_MANUAL || pivot!=PIVOT_AUTO_OVERRIDE) {
+        fPivot = pivot;
+    }
+}
+
+void MouseParameters::SetPivotVisibility(PivotVisibility visibility) {
+    fPivotVisibility = visibility;
+}
+
+#else
+
+#define WITH_SEPARATE_THREAD false    // set to true or false
+
+// Make the linker happy for the framework check (see link below for more info)
+// http://developer.apple.com/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WeakLinking.html
+
+extern int16_t SetConnexionHandlers(ConnexionMessageHandlerProc messageHandler, ConnexionAddedHandlerProc addedHandler, ConnexionRemovedHandlerProc removedHandler, bool useSeparateThread) __attribute__((weak_import));
+
+int fConnexionClientID;
+
+static ConnexionDeviceState lastState;
+
+static void DeviceAddedHandler(unsigned int connection);
+static void DeviceRemovedHandler(unsigned int connection);
+static void MessageHandler(unsigned int connection, unsigned int messageType, void *messageArgument);
+
+void ConnexionClient::toggleConnexion(bool shouldEnable)
+{
+    if (shouldEnable && !ConnexionClient::Is3dmouseAttached()) {
+        ConnexionClient::init();
+    }
+    if (!shouldEnable && ConnexionClient::Is3dmouseAttached()) {
+        ConnexionClient::destroy();
+    }
+
+}
+
+void ConnexionClient::init() {
+    // Make sure the framework is installed
+    if (SetConnexionHandlers != NULL && Menu::getInstance()->isOptionChecked(MenuOption::Connexion)) {
+        // Install message handler and register our client
+        InstallConnexionHandlers(MessageHandler, DeviceAddedHandler, DeviceRemovedHandler);
+        // Either use this to take over in our application only... does not work
+        // fConnexionClientID = RegisterConnexionClient('MCTt', "\pConnexion Client Test", kConnexionClientModeTakeOver, kConnexionMaskAll);
+
+        // ...or use this to take over system-wide
+        fConnexionClientID = RegisterConnexionClient(kConnexionClientWildcard, NULL, kConnexionClientModeTakeOver, kConnexionMaskAll);
+        ConnexionData& connexiondata = ConnexionData::getInstance();
+        memcpy(&connexiondata.clientId, &fConnexionClientID, (long)sizeof(int));
+
+        // A separate API call is required to capture buttons beyond the first 8
+        SetConnexionClientButtonMask(fConnexionClientID, kConnexionMaskAllButtons);
+
+        // use default switches 
+        ConnexionClientControl(fConnexionClientID, kConnexionCtlSetSwitches, kConnexionSwitchesDisabled, NULL);
+
+        if (ConnexionClient::Is3dmouseAttached() && connexiondata.getDeviceID() == 0) {
+          connexiondata.registerToUserInputMapper(*Application::getUserInputMapper());
+          connexiondata.assignDefaultInputMapping(*Application::getUserInputMapper());
+          UserActivityLogger::getInstance().connectedDevice("controller", "3Dconnexion");
+        }
+        //let one axis be dominant
+        //ConnexionClientControl(fConnexionClientID, kConnexionCtlSetSwitches, kConnexionSwitchDominant | kConnexionSwitchEnableAll, NULL);
+    }
+}
+
+void ConnexionClient::destroy() {
+    // Make sure the framework is installed
+    if (&InstallConnexionHandlers != NULL) {
+        // Unregister our client and clean up all handlers
+        if (fConnexionClientID) {
+            UnregisterConnexionClient(fConnexionClientID);
+        }
+        CleanupConnexionHandlers();
+        fConnexionClientID = 0;
+        ConnexionData& connexiondata = ConnexionData::getInstance();
+        if (connexiondata.getDeviceID()!=0) {
+            Application::getUserInputMapper()->removeDevice(connexiondata.getDeviceID());
+            connexiondata.setDeviceID(0);
+        }
+    }
+}
+
+void DeviceAddedHandler(unsigned int connection) {
+    ConnexionData& connexiondata = ConnexionData::getInstance();
+    if (connexiondata.getDeviceID() == 0) {
+        qCWarning(interfaceapp) << "3Dconnexion device added ";
+        connexiondata.registerToUserInputMapper(*Application::getUserInputMapper());
+        connexiondata.assignDefaultInputMapping(*Application::getUserInputMapper());
+        UserActivityLogger::getInstance().connectedDevice("controller", "3Dconnexion");
+    }
+}
+
+void DeviceRemovedHandler(unsigned int connection) {
+    ConnexionData& connexiondata = ConnexionData::getInstance();
+    if (connexiondata.getDeviceID() != 0) {
+        qCWarning(interfaceapp) << "3Dconnexion device removed";
+        Application::getUserInputMapper()->removeDevice(connexiondata.getDeviceID());
+        connexiondata.setDeviceID(0);
+    }
+}
+
+bool ConnexionClient::Is3dmouseAttached() {
+    int result;
+    if (fConnexionClientID) {
+        if (ConnexionControl(kConnexionCtlGetDeviceID, 0, &result)) {
+            return false;
+        }
+        return true;
+    }
+    return false;
+}
+
+void MessageHandler(unsigned int connection, unsigned int messageType, void *messageArgument) {
+    ConnexionDeviceState *state;
+
+    switch (messageType) {
+    case kConnexionMsgDeviceState:
+        state = (ConnexionDeviceState*)messageArgument;
+        if (state->client == fConnexionClientID) {
+            ConnexionData& connexiondata = ConnexionData::getInstance();
+            connexiondata.cc_position = { state->axis[0], state->axis[1], state->axis[2] };
+            connexiondata.cc_rotation = { state->axis[3], state->axis[4], state->axis[5] };
+
+            connexiondata.handleAxisEvent();
+            if (state->buttons != lastState.buttons) {
+                connexiondata.setButton(state->buttons);
+            }
+            memmove(&lastState, state, (long)sizeof(ConnexionDeviceState));
+        }
+        break;
+    case kConnexionMsgPrefsChanged:
+        // the prefs have changed, do something
+        break;
+    default:
+        // other messageTypes can happen and should be ignored
+        break;
+    }
+
+}
+
+#endif // __APPLE__
+
+#endif // HAVE_CONNEXIONCLIENT
diff --git a/interface/src/devices/3Dconnexion.h b/interface/src/devices/3Dconnexion.h
new file mode 100755
index 0000000000..28b4924e44
--- /dev/null
+++ b/interface/src/devices/3Dconnexion.h
@@ -0,0 +1,244 @@
+//  3DConnexion.h
+//  hifi
+//
+//  Created by Marcel Verhagen on 09-06-15.
+//  Copyright 2015 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#ifndef hifi_ConnexionClient_h
+#define hifi_ConnexionClient_h
+
+#include <qobject.h>
+#include <qlibrary.h>
+#include "InterfaceLogging.h"
+#include "Application.h"
+
+#include "ui/UserInputMapper.h"
+
+#ifndef HAVE_CONNEXIONCLIENT
+class ConnexionClient : public QObject {
+    Q_OBJECT
+public:
+    static ConnexionClient& getInstance();
+    static void init() {};
+    static void destroy() {};
+    static bool Is3dmouseAttached() { return false; };
+public slots:
+    void toggleConnexion(bool shouldEnable) {};
+};
+#endif // NOT_HAVE_CONNEXIONCLIENT
+
+#ifdef HAVE_CONNEXIONCLIENT
+// the windows connexion rawinput
+#ifdef _WIN32
+
+#include "I3dMouseParams.h"
+#include <QAbstractNativeEventFilter>
+#include <QAbstractEventDispatcher>
+#include <Winsock2.h>
+#include <windows.h>
+
+// windows rawinput parameters
+class MouseParameters : public I3dMouseParam {
+public:
+    MouseParameters();
+    ~MouseParameters();
+
+    // I3dmouseSensor interface
+    bool IsPanZoom() const;
+    bool IsRotate() const;
+    Speed GetSpeed() const;
+
+    void SetPanZoom(bool isPanZoom);
+    void SetRotate(bool isRotate);
+    void SetSpeed(Speed speed);
+
+    // I3dmouseNavigation interface
+    Navigation GetNavigationMode() const;
+    Pivot GetPivotMode() const;
+    PivotVisibility GetPivotVisibility() const;
+    bool IsLockHorizon() const;
+
+    void SetLockHorizon(bool bOn);
+    void SetNavigationMode(Navigation navigation);
+    void SetPivotMode(Pivot pivot);
+    void SetPivotVisibility(PivotVisibility visibility);
+
+    static bool Is3dmouseAttached();
+
+private:
+    MouseParameters(const MouseParameters&);
+    const MouseParameters& operator = (const MouseParameters&);
+
+    Navigation fNavigation;
+    Pivot fPivot;
+    PivotVisibility fPivotVisibility;
+    bool fIsLockHorizon;
+
+    bool fIsPanZoom;
+    bool fIsRotate;
+    Speed fSpeed;
+};
+
+class ConnexionClient : public QObject, public QAbstractNativeEventFilter {
+    Q_OBJECT
+public:
+	ConnexionClient();
+    ~ConnexionClient();
+
+	static ConnexionClient& getInstance();
+
+	ConnexionClient* client;
+    static void init();
+    static void destroy();
+
+    static bool Is3dmouseAttached();
+
+    I3dMouseParam& MouseParams();
+    const I3dMouseParam& MouseParams() const;
+
+    virtual void Move3d(HANDLE device, std::vector<float>& motionData);
+    virtual void On3dmouseKeyDown(HANDLE device, int virtualKeyCode);
+    virtual void On3dmouseKeyUp(HANDLE device, int virtualKeyCode);
+
+    virtual bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) Q_DECL_OVERRIDE
+    {
+        MSG* msg = static_cast< MSG * >(message);
+        return ConnexionClient::RawInputEventFilter(message,  result);
+    }
+
+public slots:
+    void toggleConnexion(bool shouldEnable);
+
+signals:
+    void Move3d(std::vector<float>& motionData);
+    void On3dmouseKeyDown(int virtualKeyCode);
+    void On3dmouseKeyUp(int virtualKeyCode);
+
+private:
+    bool InitializeRawInput(HWND hwndTarget);
+
+    static bool RawInputEventFilter(void* msg, long* result);
+
+    void OnRawInput(UINT nInputCode, HRAWINPUT hRawInput);
+    UINT GetRawInputBuffer(PRAWINPUT pData, PUINT pcbSize, UINT cbSizeHeader);
+    bool TranslateRawInputData(UINT nInputCode, PRAWINPUT pRawInput);
+    void On3dmouseInput();
+
+    class TInputData {
+    public:
+        TInputData() : fAxes(6) {}
+
+        bool IsZero() {
+            return (0.0f == fAxes[0] && 0.0f == fAxes[1] && 0.0f == fAxes[2] &&
+                0.0f == fAxes[3] && 0.0f == fAxes[4] && 0.0f == fAxes[5]);
+        }
+
+        int fTimeToLive; // For telling if the device was unplugged while sending data
+        bool fIsDirty;
+        std::vector<float> fAxes;
+
+    };
+
+    HWND fWindow;
+
+    // Data cache to handle multiple rawinput devices
+    std::map< HANDLE, TInputData> fDevice2Data;
+    std::map< HANDLE, unsigned long> fDevice2Keystate;
+
+    // 3dmouse parameters
+    MouseParameters f3dMouseParams;     // Rotate, Pan Zoom etc.
+
+    // use to calculate distance traveled since last event
+    DWORD fLast3dmouseInputTime;
+};
+
+// the osx connexion api
+#else
+
+#include <glm/glm.hpp>
+#include "3DconnexionClient/ConnexionClientAPI.h"
+
+class ConnexionClient : public QObject {
+    Q_OBJECT
+public:
+    static ConnexionClient& getInstance();
+    static bool Is3dmouseAttached();
+    static void init();
+    static void destroy();
+public slots:
+    void toggleConnexion(bool shouldEnable);
+};
+
+#endif // __APPLE__
+
+#endif // HAVE_CONNEXIONCLIENT
+
+
+// connnects to the userinputmapper
+class ConnexionData : public QObject {
+    Q_OBJECT
+
+public:
+    static ConnexionData& getInstance();
+    ConnexionData();
+
+    enum PositionChannel {
+        POSITION_AXIS_X_POS = 1,
+        POSITION_AXIS_X_NEG = 2,
+        POSITION_AXIS_Y_POS = 3,
+        POSITION_AXIS_Y_NEG = 4,
+        POSITION_AXIS_Z_POS = 5,
+        POSITION_AXIS_Z_NEG = 6,
+        ROTATION_AXIS_X_POS = 7,
+        ROTATION_AXIS_X_NEG = 8,
+        ROTATION_AXIS_Y_POS = 9,
+        ROTATION_AXIS_Y_NEG = 10,
+        ROTATION_AXIS_Z_POS = 11,
+        ROTATION_AXIS_Z_NEG = 12
+    };
+
+    enum ButtonChannel {
+        BUTTON_1 = 1,
+        BUTTON_2 = 2,
+        BUTTON_3 = 3
+    };
+
+    typedef std::unordered_set<int> ButtonPressedMap;
+    typedef std::map<int, float> AxisStateMap;
+
+    float getButton(int channel) const;
+    float getAxis(int channel) const;
+
+    UserInputMapper::Input makeInput(ConnexionData::PositionChannel axis);
+    UserInputMapper::Input makeInput(ConnexionData::ButtonChannel button);
+
+    void registerToUserInputMapper(UserInputMapper& mapper);
+    void assignDefaultInputMapping(UserInputMapper& mapper);
+
+    void update();
+    void focusOutEvent();
+
+    int getDeviceID() { return _deviceID; }
+    void setDeviceID(int deviceID) { _deviceID = deviceID; }
+
+    QString _name;
+
+    glm::vec3 cc_position;
+    glm::vec3 cc_rotation;
+    int clientId;
+
+    void setButton(int lastButtonState);
+    void handleAxisEvent();
+
+protected:
+    int _deviceID = 0;
+
+    ButtonPressedMap _buttonPressedMap;
+    AxisStateMap _axisStateMap;
+};
+
+#endif // defined(hifi_ConnexionClient_h)
\ No newline at end of file
diff --git a/tests/ui/src/main.cpp b/tests/ui/src/main.cpp
index f5647bd176..fdc4dbae34 100644
--- a/tests/ui/src/main.cpp
+++ b/tests/ui/src/main.cpp
@@ -107,6 +107,7 @@ public:
         CachesSize,
         Chat,
         Collisions,
+        Connexion,
         Console,
         ControlWithSpeech,
         CopyAddress,