好记性不如烂笔头。上次简单研究了一下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 joystick
VRPN_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 box
if (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 linux
fprintf(stderr, "- Is the GPM server running?\n");
fprintf(stderr,
"- Are you running on a linux console (not an xterm)?\n");
#endif
return -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 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; //最大值
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 server
protected: // 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 changed
virtual void report_changes( vrpn_uint32 class_of_service
= vrpn_CONNECTION_LOW_LATENCY );
/// send report whether or not changed
virtual void report( vrpn_uint32 class_of_service
= vrpn_CONNECTION_LOW_LATENCY );
protected: // data
struct timeval timestamp; // time of last report from device
std::vector<CJoystick> joysticks; // DXinput数据接口
std::vector<DIJOYSTATE*> pjs;
int DeviceNumToConnect = 0;
private: // disable unwanted default methods
vrpn_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 class
for (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;
#else
return 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("设置死区失败 - 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;
}
std::string CJoystick::GetLastErrMsg() { return m_errmsg; }
3. 小结
这么改完,编译一下就能用了,测试的话用vrpn_print_device那个就行,效果跟鼠标的那个一样。
至此完成了设备的添加,而且加上了Config读取的内容。