Linux で Gamepad の値を読み込む & デバイスの判定

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 のイベントコード