Vrpn源码浅析(二)-自定义创建JoyStick设备
好记性不如烂笔头。上次简单研究了一下VRPN的代码结构,梳理了一下创建自定义VRPN设备的总体流程。本来打算研究一下自定义tracker类设备,把optitrack添加进去,但是临时需要获取飞行手柄的数据,所以就先拿手柄进行测试,尝试完整添加一个自定义的设备进去,这篇博客记录了整个添加的过程。
本次添加的飞行手柄型号为Saitek X52,包含一个油门、一个操纵杆,还有一个脚踏,其中操纵杆和油门是级联在一起通过一个USB接到电脑上,脚踏式独立一个USB接到电脑上,因此对于计算机而言,连接的设备只有两个,一个是油门和操纵杆的组合,一个是脚踏。
1.获取设备数据
为了在VRPN中添加自定义设备,首先需要自己用代码获取到对应的数据。对于本次需要添加的设备,使用DirectxInput8获取(网上相关的博客很多就不详细介绍了),这部分我在网上找了一段代码改了一下(源代码只读取了一个设备,我这有两个,主要是增加了连接多个设备的内容),直接上代码,解释都在注释里。
1.1 CJoystick.h代码:
#pragma once
#pragma once#include "dinput.h"
#include "dinputd.h"
#include <string>
using namespace std;#define DIRECTINPUT_VERSION 0x0800
#define DI8DEVCLASS_GAMECTRL 4 //扫描游戏控制器#pragma comment(lib,"dxguid.lib")
#pragma comment(lib,"dinput8.lib")/********************************************************************\
功能描述:游戏手柄控制类
\********************************************************************/
class CJoystick
{
public:CJoystick(int DeviceID);~CJoystick(void);
public:string GetLastErrMsg();BOOL Init(LONG nMin = -1024, LONG nMax = 1024); //初始化函数DIJOYSTATE* PollDevice(); // 轮循设备函数,在轮循过程中读取设备状态//枚举设备static BOOL CALLBACK DIEnumDevicesCallback(const DIDEVICEINSTANCE* lpddi, VOID* pvRef); //枚举对象static BOOL CALLBACK EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* pContext);
private://一般的成员变量HINSTANCE m_hInstance; // 实例句柄LPDIRECTINPUT8 m_lpDI; // DI8接口指针LPDIRECTINPUTDEVICE8 m_lpDIDevice; // DIDevice8接口指针DIJOYSTATE m_diJs; //存储Joystick状态信息GUID JoystickGUID; //GUID,设备唯一编码,通过这个值连接设备int DeviceID;//想要连接的设备编号,即等待连接的设备序列中的第几个设备,在构造函数中确定LONG m_nMax; //最小值LONG m_nMin; //最大值string m_errmsg;
};
1.2 CJoystick.cpp代码:
#include "CJoystick.h"#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } }int DeviceNum = 0;//在设备序列中排在目标设备前的设备数
int DeviceFound = 0;//在枚举中找到的设备数目
struct DI_ENUM_CONTEXT
{DIJOYCONFIG* pPreferredJoyCfg;bool bPreferredJoyCfgValid;
};CJoystick::CJoystick(int DeviceID)
{m_lpDIDevice = NULL;m_lpDI = NULL;this->DeviceID = DeviceID;m_hInstance = GetModuleHandle(NULL); //获取实例句柄m_nMin = -1024;m_nMax = +1024;
}CJoystick::~CJoystick(void)
{//释放DI和DIDevice对象if (m_lpDIDevice){m_lpDIDevice->Unacquire();m_lpDIDevice->Release();m_lpDIDevice = NULL;}if (m_lpDI){m_lpDI->Release();m_lpDI = NULL;}
}
//初始化
BOOL CJoystick::Init(LONG nMin, LONG nMax)
{HRESULT hr;//m_hWnd = hWnd ;DeviceNum = DeviceID;DeviceFound = 0;m_nMin = nMin;m_nMax = nMax;//1.建立DI8接口if (NULL == m_lpDI){hr = DirectInput8Create(m_hInstance,DIRECTINPUT_VERSION,(REFIID)IID_IDirectInput8,(void**)&m_lpDI, //接口取值NULL);if FAILED(hr){m_errmsg = "Create 失败 - in CDIJoystick::Initialise";return false;}}DIJOYCONFIG PreferredJoyCfg = { 0 };DI_ENUM_CONTEXT enumContext;enumContext.pPreferredJoyCfg = &PreferredJoyCfg;enumContext.bPreferredJoyCfgValid = true;//是否为标准手柄,正常是需要判断的,在这里我们只用来接收标准手柄,所以就直接设置了IDirectInputJoyConfig8* pJoyConfig = NULL;if (FAILED(hr = m_lpDI->QueryInterface(IID_IDirectInputJoyConfig8, (void**)&pJoyConfig))){m_errmsg = "获取接口失败";//MessageBox(NULL, L"joystick 获取接口失败 失败", L"worning", MB_OK);return false;}PreferredJoyCfg.dwSize = sizeof(PreferredJoyCfg);if (SUCCEEDED(pJoyConfig->GetConfig(0, &PreferredJoyCfg, DIJC_GUIDINSTANCE))) // This function is expected to fail if no joystick is attached//enumContext.bPreferredJoyCfgValid = false;SAFE_RELEASE(pJoyConfig);//2.枚举设备hr = m_lpDI->EnumDevices(DI8DEVCLASS_GAMECTRL, //扫描游戏控制器DIEnumDevicesCallback, //回调函数,连接多个设备的代码在这个回调函数里改&enumContext,//将枚举的设备信息写入上下文DIEDFL_ATTACHEDONLY); //扫描安装好的和连接好的设备//printf("Device Found %d\n", DeviceFound);//printf("DeviceID %d\n\n", this->DeviceID);if (DeviceFound < this->DeviceID) {//需要连接的设备编号设置过大,未能找到足够的设备m_errmsg = "无法检测到此设备";return false;}if FAILED(hr){//OutputDebugString("枚举设备失败 - in CDIJoystick::Initialise\n");m_errmsg = "枚举设备失败";//MessageBox(NULL, L"joystick 枚举设备失败", L"worning", MB_OK);return false;}//3.创建DI8设备if (!m_lpDIDevice){hr = m_lpDI->CreateDevice(enumContext.pPreferredJoyCfg->guidInstance, &m_lpDIDevice, NULL);if FAILED(hr){//OutputDebugString("创建设备失败 - in CDIJoystick::Initialise\n");m_errmsg = "创建设备失败";//MessageBox(NULL, L"joystick 创建设备失败", L"worning", MB_OK);return false;}}//设置协作等级—— 前台模式 | 独占模式 (后台/非独占)/*DISCL_BACKGROUND|*///hr = m_lpDIDevice ->SetCooperativeLevel(m_hWnd,DISCL_FOREGROUND|DISCL_EXCLUSIVE);//if FAILED(hr) //{// //OutputDebugString("设置协作等级失败 - in CDIJoystick::Initialise\n");// m_errmsg="设置协作等级失败";// MessageBox(NULL, L"joystick 设置协作等级失败", L"worning", MB_OK);// if (m_hWnd==NULL)// MessageBox(NULL, L"窗口句柄为空", L"worning", MB_OK);// return false; //}//4.设置数据格式hr = m_lpDIDevice->SetDataFormat(&c_dfDIJoystick);if FAILED(hr){//OutputDebugString("设置数据格式失败 - in CDIJoystick::Initialise\n");m_errmsg = "设置数据格式失败";//MessageBox(NULL, L"joystick 设置数据格式失败", L"worning", MB_OK);return false;}hr = m_lpDIDevice->Acquire();//5.枚举对象hr = m_lpDIDevice->EnumObjects(EnumObjectsCallback, (VOID*)this, DIDFT_ALL);if FAILED(hr){//OutputDebugString("枚举对象失败 - in CDIJoystick::Initialise\n");m_errmsg = "枚举对象失败";//MessageBox(NULL, L"joystick 枚举对象失败", L"worning", MB_OK);return false;}return true;
}BOOL CALLBACK CJoystick::DIEnumDevicesCallback(const DIDEVICEINSTANCE* lpddi, VOID* pvRef)
{DI_ENUM_CONTEXT* pEnumContext = (DI_ENUM_CONTEXT*)pvRef;if (pEnumContext->bPreferredJoyCfgValid) {pEnumContext->pPreferredJoyCfg->guidInstance = lpddi->guidInstance; //记录当前设备信息,用来创建设备接口DeviceFound++;}if (DeviceNum > 1) { DeviceNum --;return DIENUM_CONTINUE;//返回值为DIENUM_CONTINUE时,会继续执行一遍这个cllback函数,//如果仍有设备在序列中,则会读取下一个设备的信息,//上下文(enumContext)中的当前记录的信息则会被覆盖成新设备的信息,//如果没有新的可读设备,则会自动返回DIENUM_STOP,停止枚举。}else { return DIENUM_STOP;//停止枚举,最终记录的设备信息为停止枚举时记录的设备。}}BOOL CALLBACK CJoystick::EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* pContext)
{HRESULT hr;CJoystick* js = (CJoystick*)pContext; //首先取得JS对象指针//设置游戏杆输入特性if (pdidoi->dwType & DIDFT_AXIS) //如果枚举的对象为轴{DIPROPRANGE diprg; //设置轴范围结构diprg.diph.dwSize = sizeof(DIPROPRANGE);diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);diprg.diph.dwHow = DIPH_BYID;diprg.diph.dwObj = pdidoi->dwType; // 枚举的轴diprg.lMin = js->m_nMin; //最小值diprg.lMax = js->m_nMax; //最大值// 设置轴范围 hr = js->m_lpDIDevice->SetProperty(DIPROP_RANGE, &diprg.diph);if (FAILED(hr)){//OutputDebugString("设置轴范围失败 - in CDIJoystick::EnumObjectsCallback\n");js->m_errmsg = "设置轴范围失败 - in CDIJoystick::EnumObjectsCallback";return DIENUM_STOP;}//设置死区属性,如果你使用的是电平式的游戏手柄,需要注释掉一下部分/*DIPROPDWORD dipdw; //死区结构dipdw.diph.dwSize = sizeof( dipdw );dipdw.diph.dwHeaderSize = sizeof( dipdw.diph );diprg.diph.dwObj = pdidoi->dwType; // 枚举的轴dipdw.diph.dwHow = DIPH_DEVICE;dipdw.dwData = 1000; //10%的死区hr = js->m_lpDIDevice->SetProperty(DIPROP_DEADZONE, &dipdw.diph);if( FAILED(hr)){OutputDebugString("设置死区失败 - in CDIJoystick::EnumObjectsCallback\n");return DIENUM_STOP;}*/}return DIENUM_CONTINUE;
}//获取游戏手柄(定时调用)
DIJOYSTATE* CJoystick::PollDevice()
{HRESULT hr;//DIJOYSTATE *pdjs = NULL;if (NULL == m_lpDI || NULL == m_lpDIDevice) //未获得设备return NULL;hr = m_lpDIDevice->Poll(); // 轮循设备读取当前状态if (FAILED(hr)){// 输入流中断,不能通过轮循获得任何状态值。// 所以不需要任何重置,只要再次获得设备就行。hr = m_lpDIDevice->Acquire();while (hr == DIERR_INPUTLOST){static int iCount = 0;//if (iCount>30) exit(-1); //累积30次获取设备失败,退出程序。if (iCount > 30)return NULL;iCount++;//OutputDebugString("丢失设备,轮循失败 - in CJoystick::PollDevice\n");m_errmsg = "丢失设备,轮循失败 - in CJoystick::PollDevice";hr = m_lpDIDevice->Acquire();if (SUCCEEDED(hr)) iCount = 0;} // hr也许为其他的错误.//return &m_diJs; return NULL;}// 获得输入状态,存储到成员变量 m_diJs 中if (FAILED(hr = m_lpDIDevice->GetDeviceState(sizeof(DIJOYSTATE), &m_diJs)))return NULL; // 在轮循过程中设备将为 已获得 状态return &m_diJs;
}string CJoystick::GetLastErrMsg()
{return m_errmsg;
}
1.3 主函数
#include <iostream>
#include <stdio.h>
#include "CJoystick.h"
#include <vector>
int main()
{std::vector<CJoystick>joysticks;//根据需求创建多个joystick对象for (int i = 0; i < 2; i++) {joysticks.push_back(CJoystick(i+1));//创建两个实例 }for (int i = 0; i < 2; i++) {//对两个实例进行初始化。//注意,测试的时候发现当创建多个实例时,不能采取对每个设备顺序执行创建实例+初始化的方式//必须将所有设备先创建出来,再统一执行初始化函数joysticks[i].Init();}if (joysticks.size() == 0) {std::cout << "No Device Found";return -1;}//创建设备状态存储变量DIJOYSTATE* pjs = joysticks[0].PollDevice();DIJOYSTATE* pjs2 = joysticks[1].PollDevice();std::cout << "try to find device\n";while (1) {//读取数据 pjs = joysticks[0].PollDevice();pjs2 = joysticks[1].PollDevice();if (pjs){//获取按键信息for (int i = 0; i < 32; i++){if (pjs2->rgbButtons[i] & 0x80){//此时的i值就是按下的按键值cout << "botton"<< i <<"triggered"<<endl;}}//获取模拟量信息cout << "Lx: " <<pjs->lX<<endl;cout << "LY: " << pjs->lY << endl;if (pjs2->lRz > 200) {cout << "Lx: " << pjs2->lRz << endl;}}}
}
1.4 DirectxInput8 Joystick数据结构
在函数里,我使用了DIJOYSTATE作为存储数据容器的数据类型,这个数据类型包含了如下的定义:
typedef struct DIJOYSTATE {LONG lX; /* x-axis position */LONG lY; /* y-axis position */LONG lZ; /* z-axis position */LONG lRx; /* x-axis rotation */LONG lRy; /* y-axis rotation */LONG lRz; /* z-axis rotation */LONG rglSlider[2]; /* extra axes positions */DWORD rgdwPOV[4]; /* POV directions */BYTE rgbButtons[32]; /* 32 buttons */
} DIJOYSTATE, *LPDIJOYSTATE;
有32个按钮和若干模拟量,可以自行获取。
如果需要更多的数据,DXInput8还提供了DIJOYSTATE2的数据结构,但是我不知道怎么去Poll出来,有需要的可以研究一下,DIJOYSTATE2结构如下:
typedef struct DIJOYSTATE2 {LONG lX; /* x-axis position */LONG lY; /* y-axis position */LONG lZ; /* z-axis position */LONG lRx; /* x-axis rotation */LONG lRy; /* y-axis rotation */LONG lRz; /* z-axis rotation */LONG rglSlider[2]; /* extra axes positions */DWORD rgdwPOV[4]; /* POV directions */BYTE rgbButtons[128]; /* 128 buttons */LONG lVX; /* x-axis velocity */LONG lVY; /* y-axis velocity */LONG lVZ; /* z-axis velocity */LONG lVRx; /* x-axis angular velocity */LONG lVRy; /* y-axis angular velocity */LONG lVRz; /* z-axis angular velocity */LONG rglVSlider[2]; /* extra axes velocities */LONG lAX; /* x-axis acceleration */LONG lAY; /* y-axis acceleration */LONG lAZ; /* z-axis acceleration */LONG lARx; /* x-axis angular acceleration */LONG lARy; /* y-axis angular acceleration */LONG lARz; /* z-axis angular acceleration */LONG rglASlider[2]; /* extra axes accelerations */LONG lFX; /* x-axis force */LONG lFY; /* y-axis force */LONG lFZ; /* z-axis force */LONG lFRx; /* x-axis torque */LONG lFRy; /* y-axis torque */LONG lFRz; /* z-axis torque */LONG rglFSlider[2]; /* extra axes forces */
} DIJOYSTATE2, *LPDIJOYSTATE2;
2. VRPN添加设备
在成功获取设备信息后,我们现在就可以将代码移植到VRPN中。上一篇中我添加了一个叫MyMouse的设备,因为懒得再从新添加一遍各种文件和代码,所以直接在MyMouse中修改。不同于Mouse这种不需要参数的设备,我们需要指定连接设备的数目,所以需要利用Config文件中的参数来传递需要连接设备数目的参数值。示例配置如下,MyMouse0为设备实例的名称,2代表我们需要连接两个设备。
vrpn_MyMouse MyMouse0 2
2.1 vrpn_Generic_server_object代码修改
这部分代码的修改主要集中在setup_MyMouse函数的修改。主要是读取并解析Config文件中的设置参数,并将其传递到设备的构造函数中,修改后的代码如下:
int vrpn_Generic_Server_Object::setup_MyMouse(char *&pch, char *line,FILE * /*config_file*/)
{char s2[LINESIZE];int count = 0;//Number of joystickVRPN_CONFIG_NEXT();// Get the arguments (class, Mymouse_name)int ParamCount = sscanf(pch, "%511s%d", s2, &count);if (ParamCount < 1) {fprintf(stderr, "Bad vrpn_MyMouse line: %s\n", line); return -1;}if (count < 1) {printf("Device Number Error");return -1;}// Open the boxif (verbose) {printf("Opening vrpn_MyMouse: %s\n", s2);}try {_devices->add(new vrpn_MyMouse(s2, connection,count));//在vrpn服务中初始化并添加设备实例}catch (...) {fprintf(stderr, "could not create vrpn_MyMouse\n");
#ifdef linuxfprintf(stderr, "- Is the GPM server running?\n");fprintf(stderr,"- Are you running on a linux console (not an xterm)?\n");
#endifreturn -1;}return 0;
}
2.2 Vrpn_MyMouse.h
剩下就是修改Vrpn_MyMouse的代码了,先是头文件。
#ifndef VRPN_MYMOUSE_H
#define VRPN_MYMOUSE_H///
#include "vrpn_Analog.h" // for vrpn_Analog
#include "vrpn_Button.h" // for vrpn_Button_Filter
#include "vrpn_Configure.h" // for VRPN_API
#include "vrpn_Connection.h" // for vrpn_CONNECTION_LOW_LATENCY, etc
#include "vrpn_Shared.h" // for timeval
#include "vrpn_Types.h" // for vrpn_uint32
#include "vrpn_Text.h"
#include "dinput.h"
#include "dinputd.h"
#include <string>
#include <vector>#define DIRECTINPUT_VERSION 0x0800
#define DI8DEVCLASS_GAMECTRL 4 //扫描游戏控制器#pragma comment(lib, "dxguid.lib")//这俩lib要加到工程目录里
#pragma comment(lib, "dinput8.lib")class CJoystick {
public:CJoystick(int DeviceID);//构造函数要改一下,加一个设备连接数目的变量~CJoystick(void);public:std::string GetLastErrMsg();BOOL Init(LONG nMin = -1024, LONG nMax = 1024); //初始化函数DIJOYSTATE* PollDevice(); // 轮循设备函数,在轮循过程中读取设备状态//枚举设备static BOOL CALLBACK DIEnumDevicesCallback(const DIDEVICEINSTANCE* lpddi,VOID* pvRef); //枚举对象static BOOL CALLBACKEnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* pContext);private://一般的成员变量HINSTANCE m_hInstance; // 实例句柄LPDIRECTINPUT8 m_lpDI; // DI8接口指针LPDIRECTINPUTDEVICE8 m_lpDIDevice; // DIDevice8接口指针DIJOYSTATE m_diJs; //存储Joystick状态信息GUID JoystickGUID; // GUIDint DeviceID;LONG m_nMax; //最小值LONG m_nMin; //最大值std::string m_errmsg;
};class VRPN_API vrpn_MyMouse :public vrpn_Analog,public vrpn_Button_Filter,public vrpn_Text_Sender
{
public:vrpn_MyMouse( const char* name, vrpn_Connection* cxn,int DeviceCount );virtual ~vrpn_MyMouse();virtual void mainloop();class GpmOpenFailure {}; // thrown when can't open GPM serverprotected: // methods/// Try to read reports from the device./// Returns 1 if msg received, or 0 if none received.virtual int get_report();/// send report iff changedvirtual void report_changes( vrpn_uint32 class_of_service= vrpn_CONNECTION_LOW_LATENCY );/// send report whether or not changedvirtual void report( vrpn_uint32 class_of_service= vrpn_CONNECTION_LOW_LATENCY );protected: // datastruct timeval timestamp; // time of last report from devicestd::vector<CJoystick> joysticks; // DXinput数据接口std::vector<DIJOYSTATE*> pjs;int DeviceNumToConnect = 0;private: // disable unwanted default methodsvrpn_MyMouse();vrpn_MyMouse(const vrpn_MyMouse&);const vrpn_MyMouse& operator=(const vrpn_MyMouse&);
};#endif
2.3 Vrpn_MyMouse.C
#include <stdio.h> // for NULL, fprintf, printf, etc
#include <string.h> // for strncpy#include "vrpn_BaseClass.h" // for ::vrpn_TEXT_ERROR
#include "vrpn_MyMouse.h"
#include "vrpn_Serial.h" // for vrpn_open_commport, etc#if defined(linux) && defined(VRPN_USE_GPM_MOUSE)
#include <gpm.h> // for Gpm_Event, Gpm_Connect, etc
#endif#if !( defined(_WIN32) && defined(VRPN_USE_WINSOCK_SOCKETS) )
# include <sys/select.h> // for select, FD_ISSET, FD_SET, etc
#endif#ifdef _WIN32
#include <windows.h>#pragma comment (lib, "user32.lib")// Fix sent in by Andrei State to make this compile under Visual Studio 6.0.
// If you need this, you also have to copy multimon.h from the DirectX or
// another Windows SDK into a place where the compiler can find it.
#ifndef SM_XVIRTUALSCREEN
#define COMPILE_MULTIMON_STUBS
#include "multimon.h"
#endif#endif//变量定义
#define SAFE_RELEASE(p) \{ \if (p) { \(p)->Release(); \(p) = NULL; \} \}int DeviceNum = 0;
int DeviceFound = 0;
struct DI_ENUM_CONTEXT {DIJOYCONFIG* pPreferredJoyCfg;bool bPreferredJoyCfgValid;
};
///
//DeviceCount:需要连接的设备数目,从configure文件中读取获得,在vrpn_Generic_server.object.c的setup函数中处理
vrpn_MyMouse::vrpn_MyMouse( const char* name, vrpn_Connection * cxn,int DeviceCount ) :vrpn_Analog( name, cxn ),vrpn_Button_Filter( name, cxn ), vrpn_Text_Sender(name, cxn)
{int i;// initialize the vrpn_Analog//定义模拟量输出为12个通道,按顺序对应DirectInput的lx,ly,lz,lrx,lry,lrz,rglSlider[2],rgdwPov[4]vrpn_Analog::num_channel = 12 * DeviceCount;for( i = 0; i < vrpn_Analog::num_channel; i++) {vrpn_Analog::channel[i] = vrpn_Analog::last[i] = 0;}//将数值初始化,last应该是记录的上一次的数值,用来和当前数据对比计算变化,vrpn_print_device应该是用了这俩数判断数据是否发生变化,发生变化后将新的数据打印出来。// initialize the vrpn_Button_Filter//定义三个按钮,对应DirectInput定义的32个值vrpn_Button_Filter::num_buttons = 32 * DeviceCount;for( i = 0; i < vrpn_Button_Filter::num_buttons; i++) {vrpn_Button_Filter::buttons[i] = vrpn_Button_Filter::lastbuttons[i] = 0;}//和上边一样DeviceNumToConnect = DeviceCount;// initialize the joystick classfor (i = 0; i < DeviceCount; i++) {joysticks.push_back(CJoystick(i + 1));}for (i = 0; i < DeviceCount; i++) {if (!joysticks[i].Init()) {printf("Error in initializing, maybe the Device Number set in Configure file doesn't match the real device number, pls check");exit(-1);}pjs.push_back(joysticks[i].PollDevice());}}///vrpn_MyMouse::~vrpn_MyMouse()
{}///
//循环函数,服务起来后就是不断执行里面的两个函数
void vrpn_MyMouse::mainloop()
{get_report();//获取设备数据,自定义设备时需要修改的代码!!!!!!server_mainloop();//响应客户端查询请求,告知客户端服务器仍在线,在mainloop中必须添加
}///
//获取设备数据的代码
int vrpn_MyMouse::get_report()
{ int deviceC = 0;
#if defined(_WIN32) for (int i = 0; i < DeviceNumToConnect; i++) { pjs[i] = joysticks[i].PollDevice();if (pjs[i]) {deviceC = 32 * i;for (int j = 0; j < 32; j++) {if (pjs[i]->rgbButtons[j] & 0x80) {//此时的i值就是按下的按键值vrpn_Button::buttons[deviceC+j] = 1;}else {vrpn_Button::buttons[deviceC + j] = 0;}}deviceC = 12 * i;vrpn_Analog::channel[deviceC + 0] = (vrpn_float64)(pjs[i]->lX);vrpn_Analog::channel[deviceC + 1] = (vrpn_float64)(pjs[i]->lY);vrpn_Analog::channel[deviceC + 2] = (vrpn_float64)(pjs[i]->lZ);vrpn_Analog::channel[deviceC + 3] = (vrpn_float64)(pjs[i]->lRx);vrpn_Analog::channel[deviceC + 4] = (vrpn_float64)(pjs[i]->lRy);vrpn_Analog::channel[deviceC + 5] = (vrpn_float64)(pjs[i]->lRz);vrpn_Analog::channel[deviceC + 6] = (vrpn_float64)(pjs[i]->rglSlider[0]);vrpn_Analog::channel[deviceC + 7] = (vrpn_float64)(pjs[i]->rglSlider[1]);vrpn_Analog::channel[deviceC + 8] = (vrpn_float64)(pjs[i]->rgdwPOV[0]);vrpn_Analog::channel[deviceC + 9] = (vrpn_float64)(pjs[i]->rgdwPOV[1]);vrpn_Analog::channel[deviceC + 10] = (vrpn_float64)(pjs[i]->rgdwPOV[2]);vrpn_Analog::channel[deviceC + 11] = (vrpn_float64)(pjs[i]->rgdwPOV[3]);}}vrpn_gettimeofday( ×tamp, NULL );//获取时间戳report_changes();return 1;
#elsereturn 0;
#endif
}///
//当数据变化时发送
void vrpn_MyMouse::report_changes( vrpn_uint32 class_of_service )
{vrpn_Analog::timestamp = timestamp;vrpn_Button_Filter::timestamp = timestamp;vrpn_Analog::report_changes( class_of_service );//变化时输出,用这个就只有移动鼠标时接到输出vrpn_Button_Filter::report_changes();//vrpn_Text_Sender::send_message("hello", vrpn_TEXT_NORMAL);
}///
//不论数据是否变化都发送
void vrpn_MyMouse::report( vrpn_uint32 class_of_service )
{vrpn_Analog::timestamp = timestamp;vrpn_Button_Filter::timestamp = timestamp;vrpn_Analog::report( class_of_service );//不论变化与否都输出,用这个就会一直接收到鼠标的位置信息vrpn_Button_Filter::report_changes();
}//Joystick连接相关函数(Direct Input)
CJoystick::CJoystick(int DeviceID)
{m_lpDIDevice = NULL;m_lpDI = NULL;this->DeviceID = DeviceID;m_hInstance = GetModuleHandle(NULL); //获取实例句柄m_nMin = -1024;m_nMax = +1024;
}CJoystick::~CJoystick(void)
{//释放DI和DIDevice对象if (m_lpDIDevice) {m_lpDIDevice->Unacquire();m_lpDIDevice->Release();m_lpDIDevice = NULL;}if (m_lpDI) {m_lpDI->Release();m_lpDI = NULL;}
}
//初始化
BOOL CJoystick::Init(LONG nMin, LONG nMax)
{HRESULT hr;// m_hWnd = hWnd ;DeviceNum = DeviceID;DeviceFound = 0;m_nMin = nMin;m_nMax = nMax;// 1.建立DI8接口if (NULL == m_lpDI) {hr = DirectInput8Create(m_hInstance, DIRECTINPUT_VERSION,(REFIID)IID_IDirectInput8,(void**)&m_lpDI, //接口取值NULL);if FAILED (hr) {// OutputDebugString("Create 失败 - in CDIJoystick::Initialise\n");m_errmsg = "Create 失败 - in CDIJoystick::Initialise";// MessageBox(NULL, L"joystick Create 失败", L"worning", MB_OK);return false;}}DIJOYCONFIG PreferredJoyCfg = {0};DI_ENUM_CONTEXT enumContext;enumContext.pPreferredJoyCfg = &PreferredJoyCfg;enumContext.bPreferredJoyCfgValid = true; //是否为标准手柄IDirectInputJoyConfig8* pJoyConfig = NULL;if (FAILED(hr = m_lpDI->QueryInterface(IID_IDirectInputJoyConfig8,(void**)&pJoyConfig))) {m_errmsg = "获取接口失败";// MessageBox(NULL, L"joystick 获取接口失败 失败", L"worning", MB_OK);return false;}PreferredJoyCfg.dwSize = sizeof(PreferredJoyCfg);if (SUCCEEDED(pJoyConfig->GetConfig(0, &PreferredJoyCfg,DIJC_GUIDINSTANCE))) // This function is expected to fail if no// joystick is attached// enumContext.bPreferredJoyCfgValid = false;SAFE_RELEASE(pJoyConfig);// 2.枚举设备hr = m_lpDI->EnumDevices(DI8DEVCLASS_GAMECTRL, //扫描游戏控制器DIEnumDevicesCallback, //回调函数&enumContext,DIEDFL_ATTACHEDONLY); //扫描安装好的和连接好的设备//printf("Device Found %d\n", DeviceFound);//printf("DeviceID %d\n\n", this->DeviceID);if (DeviceFound < this->DeviceID) {m_errmsg = "无法检测到此设备";return false;}if FAILED (hr) {// OutputDebugString("枚举设备失败 - in CDIJoystick::Initialise\n");m_errmsg = "枚举设备失败";// MessageBox(NULL, L"joystick 枚举设备失败", L"worning", MB_OK);return false;}// 3.创建DI8设备if (!m_lpDIDevice) {// enumContext.pPreferredJoyCfg->guidInstance.Data4[1] = 2;hr = m_lpDI->CreateDevice(enumContext.pPreferredJoyCfg->guidInstance,&m_lpDIDevice, NULL);// printf("enumContext.pPreferredJoyCfg->guidInstance = %d\n",// enumContext.pPreferredJoyCfg->guidInstance.Data4[1]);if FAILED (hr) {// OutputDebugString("创建设备失败 - in CDIJoystick::Initialise\n");m_errmsg = "创建设备失败";// MessageBox(NULL, L"joystick 创建设备失败", L"worning", MB_OK);return false;}}//设置协作等级—— 前台模式 | 独占模式 (后台/非独占)/*DISCL_BACKGROUND|*/// hr = m_lpDIDevice// ->SetCooperativeLevel(m_hWnd,DISCL_FOREGROUND|DISCL_EXCLUSIVE); if// FAILED(hr)//{// //OutputDebugString("设置协作等级失败 - in CDIJoystick::Initialise\n");// m_errmsg="设置协作等级失败";// MessageBox(NULL, L"joystick 设置协作等级失败", L"worning", MB_OK);// if (m_hWnd==NULL)// MessageBox(NULL, L"窗口句柄为空", L"worning", MB_OK);// return false;//}// 4.设置数据格式hr = m_lpDIDevice->SetDataFormat(&c_dfDIJoystick);if FAILED (hr) {// OutputDebugString("设置数据格式失败 - in CDIJoystick::Initialise\n");m_errmsg = "设置数据格式失败";// MessageBox(NULL, L"joystick 设置数据格式失败", L"worning", MB_OK);return false;}hr = m_lpDIDevice->Acquire();// 5.枚举对象hr = m_lpDIDevice->EnumObjects(EnumObjectsCallback, (VOID*)this, DIDFT_ALL);if FAILED (hr) {// OutputDebugString("枚举对象失败 - in CDIJoystick::Initialise\n");m_errmsg = "枚举对象失败";// MessageBox(NULL, L"joystick 枚举对象失败", L"worning", MB_OK);return false;}return true;
}BOOL CALLBACK CJoystick::DIEnumDevicesCallback(const DIDEVICEINSTANCE* lpddi,VOID* pvRef)
{DI_ENUM_CONTEXT* pEnumContext = (DI_ENUM_CONTEXT*)pvRef;if (pEnumContext->bPreferredJoyCfgValid) {pEnumContext->pPreferredJoyCfg->guidInstance = lpddi->guidInstance;DeviceFound++;}if (DeviceNum > 1) {DeviceNum--;return DIENUM_CONTINUE;}else {return DIENUM_STOP;}
}BOOL CALLBACK CJoystick::EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* pContext)
{HRESULT hr;CJoystick* js = (CJoystick*)pContext; //首先取得JS对象指针//设置游戏杆输入特性if (pdidoi->dwType & DIDFT_AXIS) //如果枚举的对象为轴{DIPROPRANGE diprg; //设置轴范围结构diprg.diph.dwSize = sizeof(DIPROPRANGE);diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);diprg.diph.dwHow = DIPH_BYID;diprg.diph.dwObj = pdidoi->dwType; // 枚举的轴diprg.lMin = js->m_nMin; //最小值diprg.lMax = js->m_nMax; //最大值// 设置轴范围hr = js->m_lpDIDevice->SetProperty(DIPROP_RANGE, &diprg.diph);if (FAILED(hr)) {// OutputDebugString("设置轴范围失败 - in// CDIJoystick::EnumObjectsCallback\n");js->m_errmsg ="设置轴范围失败 - in CDIJoystick::EnumObjectsCallback";return DIENUM_STOP;}//设置死区属性,如果你使用的是电平式的游戏手柄,需要注释掉一下部分/*DIPROPDWORD dipdw; //死区结构dipdw.diph.dwSize = sizeof( dipdw );dipdw.diph.dwHeaderSize = sizeof( dipdw.diph );diprg.diph.dwObj = pdidoi->dwType; // 枚举的轴dipdw.diph.dwHow = DIPH_DEVICE;dipdw.dwData = 1000; //10%的死区hr = js->m_lpDIDevice->SetProperty(DIPROP_DEADZONE, &dipdw.diph);if( FAILED(hr)){OutputDebugString("设置死区失败 - inCDIJoystick::EnumObjectsCallback\n"); return DIENUM_STOP;}*/}return DIENUM_CONTINUE;
}//获取游戏手柄(定时调用)
DIJOYSTATE* CJoystick::PollDevice()
{HRESULT hr;// DIJOYSTATE *pdjs = NULL;if (NULL == m_lpDI || NULL == m_lpDIDevice) //未获得设备return NULL;hr = m_lpDIDevice->Poll(); // 轮循设备读取当前状态if (FAILED(hr)) {// 输入流中断,不能通过轮循获得任何状态值。// 所以不需要任何重置,只要再次获得设备就行。hr = m_lpDIDevice->Acquire();while (hr == DIERR_INPUTLOST) {static int iCount = 0;// if (iCount>30) exit(-1); //累积30次获取设备失败,退出程序。if (iCount > 30) return NULL;iCount++;// OutputDebugString("丢失设备,轮循失败 - in// CJoystick::PollDevice\n");m_errmsg = "丢失设备,轮循失败 - in CJoystick::PollDevice";hr = m_lpDIDevice->Acquire();if (SUCCEEDED(hr)) iCount = 0;} // hr也许为其他的错误.// return &m_diJs;return NULL;}// 获得输入状态,存储到成员变量 m_diJs 中if (FAILED(hr = m_lpDIDevice->GetDeviceState(sizeof(DIJOYSTATE), &m_diJs)))return NULL; // 在轮循过程中设备将为 已获得 状态return &m_diJs;
}std::string CJoystick::GetLastErrMsg() { return m_errmsg; }
3. 小结
这么改完,编译一下就能用了,测试的话用vrpn_print_device那个就行,效果跟鼠标的那个一样。
至此完成了设备的添加,而且加上了Config读取的内容。
Vrpn源码浅析(二)-自定义创建JoyStick设备相关推荐
- Vrpn源码浅析(三)-添加optitrack追踪设备
好记性不如烂笔头,之前进行了源码的简单分析并尝试添加了joystick这类包含analog以及button类型数据的设备.这次我们更近一步,尝试添加最为复杂的追踪设备.本次添加的设备为optitrac ...
- Spring源码解析二之创建Bean(实例化)
上一节我们分析到了createBean,而真正创建Bean的过程在doCreateBean过程,我们可以发现Spring的编码风格,do才是真正的过程,不带do的通常是在做在准备过程,并且我们跳过了一 ...
- LinkedList类源码浅析(二)
1.上一节介绍了LinkedList的几个基本的方法,其他方法类似,就不一一介绍: 现在再来看一个删除的方法:remove(Object o) remove方法接受一个Object参数,这里需要对参数 ...
- tio-http-server 源码浅析(二)Http请求的处理HttpRequestHandler
前言 在上一篇<tio-http-server 源码浅析(一)HttpRequestDecoder的实现>简单分析了HttpRequestDecoder的源码,并且已经得到了HttpReq ...
- Joolun uniapp商城源码实现商城自定义拖拽装修_Java二开源码
前一阵子,Joolun uniapp商城源码系统应广大开发者的需求,把开发者们想要的商城自定义拖拽装修给安排上了,拖拽装修也在V1.0.2版本发布的时候正式上线,实现商城首页自定义动态拖拽装修(支持不 ...
- Android Loader机制全面详解及源码浅析
原文出处:csdn@工匠若水,http://blog.csdn.net/yanbober/article/details/48861457 一.概述 在Android中任何耗时的操作都不能放在UI主线 ...
- 【flink】Flink 1.12.2 源码浅析 : yarn-per-job模式解析 从脚本到主类
1.概述 转载:Flink 1.12.2 源码浅析 : yarn-per-job模式解析 [一] 可以去看原文.这里是补充专栏.请看原文 2. 前言 主要针对yarn-per-job模式进行代码分析. ...
- 深入探索Android 启动优化(七) - JetPack App Startup 使用及源码浅析
本文首发我的微信公众号:徐公,想成为一名优秀的 Android 开发者,需要一份完备的 知识体系,在这里,让我们一起成长,变得更好~. 前言 前一阵子,写了几篇 Android 启动优化的文章,主要是 ...
- Spring-Web(一) RestTemplate使用与源码浅析
Spring RestTemplate使用与源码浅析 一.RestTemplate 概述 RestTemplate 是 Spring Web 模块封装的一个基于Rest规范提供HTTP请求服务的工 ...
最新文章
- Webwork 学习之路(四)Configuration 详解
- 点击图片放大至原始图片大小
- Monty Hall 问题与贝叶斯定理的理解
- swing中在JTextPane中的输入窗口出现乱码的问题
- python爬虫JS逆向之人口流动态势大数据
- java swing 复选JCheckBox组件美化
- eclipse与myeclipse恢复已删除的文件和代码
- 数组转ArrayList的正确方式
- GD32(6)中文字库
- 鸟哥的 linux 的私房菜 基础学习篇,鸟哥的 Linux 私房菜 -- 基础学习篇
- Matlab|基于粒子群优化算法及鲁棒MPPT控制器提高光伏并网的效率
- 除了秀米,微信排版还有什么好用的? ---短网址
- 09.第十章.项目沟通和干系人管理
- 这几款可以识别图片文字的app值得一试
- 堆转存目录/tmp或日志目录/var/log可用空间小于 10.0 吉字节。
- 13.3.8 添加换行符和换页符
- JS移动DOM节点,将某节点下所有子节点移动(剪切)到另一个节点下。新手很容易踩的坑!
- JAVA光头之路(一)--环境变量
- 已解决vue-router4路由报“[Vue Router warn]: No match found for location with path“
- OpenCV 学习笔记-day13 像素值统计 统计计算最大最小值,平均值和标准差 (minMaxLoc()和meanStdDev()函数的使用)