Linux では接続した Gamepad の情報を /dev/input/js* から読み取ることができます。
構造体などは /usr/include/linux/joystick.h で定義されており、
具体的な方法は下記ドキュメントの通り。
・joystick-api.txt
int fd= open( "/dev/input/js0", O_RDONLY );
unsigned char ButtonData[BUTTON_DATA_MAX];
signed int StickData[STICK_DATA_MAX];
for(;;){
struct js_event event;
if( read( fd, &event, sizeof(struct js_event) ) >= sizeof(struct js_event) ){
switch( event.type & 0x7f ){
case JS_EVENT_BUTTON:
if( event.number < BUTTON_DATA_MAX ){
ButtonData[ event.number ]= event.value != 0;
}
break;
case JS_EVENT_AXIS:
if( event.number < STICK_DATA_MAX ){
StickData[ event.number ]= event.value;
}
break;
}
}
}
close( fd );
read() はイベントが発生するまで block するので、スレッドを使うか
他の IO のように select() を用いることが可能です。
ボタンやスティックの配列はコントローラ毎に異なっています。
アプリケーション側で配列の変更が必要です。
Windows の DirectInput で認識する配列とも異なっているようです。
とりあえず下記コントローラで動作を確認しました。
・PS3 コントローラ (USB 接続)
・Xbox 360 USB コントローラ
・PS4 DUALSHOCK4 (USB 接続)
PS3コントローラは各ボタンの感圧情報も送られてきます。
使用した環境は下記の通り。
Ubuntu 13.10 x86_x64 (64bit)
● デバイスの種類の判定
自分の環境では Gamepad だけでなく、ワイヤレスマウスのレシーバーも
/dev/input/js* に列挙されていました。
そのままでは不便なので、Gamepad かどうかの識別を行います。
udevadm info /dev/input/js0
上記コマンドでは正しく ID_INPUT_JOYSTICK と識別されているので、
udevadm の手法を調べてみました。
まず stat() で /dev/input/js* の device 番号を調べます。
struct stat file_stat;
stat( "/dev/input/js0", &file_stat );
assert( S_ISCHR( file_stat.st_mode ) );
int dev_major= major( file_stat.st_rdev );
int dev_minor= minor( file_stat.st_rdev );
キャラクタデバイスなら "/sys/dev/char/" 以下に "major:minor" という
名前でリンクが作られているので、それを開きます。
その中の device/capabilities 以下に、対応しているボタンなどの
情報が格納されています。
例えば major,minor が 13,0 なら
/sys/dev/char/13:0/device/capabilities/key
中身は text で long 型の bit mask です。
udevadm では、この bitmask を読み取ってゲームコントローラ用のボタンが
存在してるかどうかで判別を行っていました。
ボタンなどのシンボルは /usr/include/linux/input.h で定義されています。
static bool ReadCaps( int dev_major, int dev_minor, const char* file_name, CapsType& caps )
{
caps.Clear();
char cap_name[CAP_NAME_MAX];
sprintf_s( cap_name, CAP_NAME_MAX, "/sys/dev/char/%d:%d/device/capabilities/%s", file_name, dev_major, dev_minor );
int fd= open( cap_name, O_RDONLY );
if( fd < 0 ){
return false;
}
char caps_buffer[STAT_MAX_SIZE];
memset( caps_buffer, 0, STAT_MAX_SIZE );
read( fd, caps_buffer, STAT_MAX_SIZE-1 );
close( fd );
const char* cp= caps_buffer;
for(; *cp && *cp != '\n' ;){
unsigned long value= strtoul( cp, NULL, 16 );
for(; *cp && *cp != ' ' ; cp++ );
for(; *cp == ' ' || *cp == '\n' ; cp++ );
caps.Push( value );
}
return true;
}
bool IsJoystick( const char* device_path )
{
struct stat file_stat;
stat( device_path, &file_stat );
assert( S_ISCHR( file_stat.st_mode ) );
int dev_major= major( file_stat.st_rdev );
int dev_minor= minor( file_stat.st_rdev );
CapsType caps_ev;
CapsType caps_key;
CapsType caps_abs;
ReadCaps( dev_major, dev_minor, "ev", caps_key );
ReadCaps( dev_major, dev_minor, "key", caps_key );
ReadCaps( dev_major, dev_minor, "abs", caps_key );
if( caps_ev.TestBit( EV_ABS ) && caps_abs.TestBit( ABS_X ) && caps_abs.TestBit( ABS_Y ) ){
if( cap_key.TestBit( BTN_A ) || caps_key.TestBit( BTN_TRIGGER ) || caps_key.TestBit( BTN_1 ) ){
return true;
}
}
return false;
}
abs に ABS_X / ABS_Y のアナログの絶対座標データが含まれており、
かつ key に BTN_A / BTN_TRIGGER / BTN_1 のいずれかが存在している場合に
JOYSTICK とみなしています。
class CapsType {
public:
enum {
CAPS_BUFFER_MAX= 32,
};
private:
unsigned long CapsBuffer[CAPS_BUFFER_MAX];
unsigned int Index;
public:
CapsType() : Index( 0 )
{
Clear();
}
void Clear()
{
memset( CapsBuffer, 0, sizeof(unsigned long) * CAPS_BUFFER_MAX );
Index= 0;
}
void Push( unsigned long value )
{
if( Index < CAPS_BUFFER_MAX ){
CapsBuffer[Index++]= value;
}
}
bool TestBit( unsigned int bit_id ) const
{
const int LONG_BIT_SIZE= sizeof(unsigned long) * 8;
unsigned int bit_no= bit_id & (LONG_BIT_SIZE-1);
unsigned int data_no= bit_id / LONG_BIT_SIZE;
if( data_no < Index ){
return ( CapsBuffer[Index - data_no -1] & (1UL<
Linux は LP64 なので long 型のサイズは可変です。
64bit OS なら 64bit, 32bit なら 32bit となる点に注意。
32bit 時は CAPS_BUFFER_MAX が不足する可能性があるため、厳密には
/usr/include/linux/input.h で定義されている EV_MAX, KEY_MAX, ABS_MAX
からバッファサイズを求めた方が安全です。
bitmask は上位データから並んでいるので、読み込んだあとのテーブルが
逆順になる点も注意が必要です。
関連エントリ
・Linux 他 X11 で OpenGL 4 を使う
・Android 3.1 と GamePad のイベントの詳細 (2)
・Android 3.1 と GamePad のイベントコード