// // 3DConnexionClient.cpp // interface/src/devices // // 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 "3DConnexionClient.h" #include "Menu.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() { _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 { QVector 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 detached // 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_3DCONNEXIONCLIENT #ifdef Q_OS_WIN #include void ConnexionClient::toggleConnexion(bool shouldEnable) { ConnexionData& connexiondata = ConnexionData::getInstance(); if (shouldEnable && connexiondata.getDeviceID() == 0) { init(); } if (!shouldEnable && connexiondata.getDeviceID() != 0) { destroy(); } } void ConnexionClient::init() { if (Menu::getInstance()->isOptionChecked(MenuOption::Connexion)) { fLast3dmouseInputTime = 0; InitializeRawInput(GetActiveWindow()); QAbstractEventDispatcher::instance()->installNativeEventFilter(this); } } void ConnexionClient::destroy() { QAbstractEventDispatcher::instance()->removeNativeEventFilter(this); ConnexionData& connexiondata = ConnexionData::getInstance(); int deviceid = connexiondata.getDeviceID(); connexiondata.setDeviceID(0); auto userInputMapper = DependencyManager::get(); userInputMapper->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(SpacePilotKeys), CONNEXIONPID_SPACEEXPLORER, sizeof(SpaceExplorerKeys) / sizeof(SpaceExplorerKeys[0]), const_cast(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(); if (Is3dmouseAttached() && connexiondata.getDeviceID() == 0) { connexiondata.registerToUserInputMapper(*userInputMapper); connexiondata.assignDefaultInputMapping(*userInputMapper); UserActivityLogger::getInstance().connectedDevice("controller", "3Dconnexion"); } else if (!Is3dmouseAttached() && connexiondata.getDeviceID() != 0) { int deviceid = connexiondata.getDeviceID(); connexiondata.setDeviceID(0); userInputMapper->removeDevice(deviceid); } if (!Is3dmouseAttached()) { return false; } MSG* message = (MSG*)(msg); if (message->message == WM_INPUT) { HRAWINPUT hRawInput = reinterpret_cast(message->lParam); OnRawInput(RIM_INPUT, hRawInput); if (result != 0) { result = 0; } return true; } return false; } // 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& 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(nDevices); if (::GetRawInputDeviceList(&rawInputDeviceList[0], &nDevices, sizeof(RAWINPUTDEVICELIST)) == static_cast(-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; } 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 (IsWindowsVistaOrGreater()) { 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(msg.lParam); size_t cbSize = *pcbSize - cbDataSize; if (::GetRawInputData(hRawInput, RID_INPUT, pri, &cbSize, cbSizeHeader) == static_cast(-1)) { if (nCount == 0) { return static_cast(-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(pri)-reinterpret_cast(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::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::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 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(pBuffer); UINT cbSize = cbSizeOfBuffer; if (::GetRawInputData(hRawInput, RID_INPUT, pRawInput, &cbSize, sizeof(RAWINPUTHEADER)) == static_cast(-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(-1)) { PRAWINPUT pri = pRawInput; UINT nInput; for (nInput = 0; nInputGetRawInputBuffer(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); // 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 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(&pRawInput->data.hid.bRawData[1]); // Cache the pan zoom data deviceData.fAxes[0] = static_cast(pnRawData[0]); deviceData.fAxes[1] = static_cast(pnRawData[1]); deviceData.fAxes[2] = static_cast(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(pnRawData[3]); deviceData.fAxes[4] = static_cast(pnRawData[4]); deviceData.fAxes[5] = static_cast(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(&pRawInput->data.hid.bRawData[1]); // Cache the rotation data deviceData.fAxes[3] = static_cast(pnRawData[0]); deviceData.fAxes[4] = static_cast(pnRawData[1]); deviceData.fAxes[5] = static_cast(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(&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) { } 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 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 && !Is3dmouseAttached()) { init(); } if (!shouldEnable && Is3dmouseAttached()) { destroy(); } } void ConnexionClient::init() { // Make sure the framework is installed if (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 (Is3dmouseAttached() && connexiondata.getDeviceID() == 0) { auto userInputMapper = DependencyManager::get(); connexiondata.registerToUserInputMapper(*userInputMapper); connexiondata.assignDefaultInputMapping(*userInputMapper); 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) { auto userInputMapper = DependencyManager::get(); userInputMapper->removeDevice(connexiondata.getDeviceID()); connexiondata.setDeviceID(0); } } } void DeviceAddedHandler(unsigned int connection) { ConnexionData& connexiondata = ConnexionData::getInstance(); if (connexiondata.getDeviceID() == 0) { qCWarning(interfaceapp) << "3Dconnexion device added "; auto userInputMapper = DependencyManager::get(); connexiondata.registerToUserInputMapper(*userInputMapper); connexiondata.assignDefaultInputMapping(*userInputMapper); UserActivityLogger::getInstance().connectedDevice("controller", "3Dconnexion"); } } void DeviceRemovedHandler(unsigned int connection) { ConnexionData& connexiondata = ConnexionData::getInstance(); if (connexiondata.getDeviceID() != 0) { qCWarning(interfaceapp) << "3Dconnexion device removed"; auto userInputMapper = DependencyManager::get(); userInputMapper->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_3DCONNEXIONCLIENT