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 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( &timestamp, 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读取的内容。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值