VIVEトラッカーのボタンを活用する話

# はじめに
VRChatをプレイ中、GeForce Experienceで録画するときに毎回キーボードを探して入力するのが面倒だったのですが、トラッカーのボタン使えたら便利じゃね？と思ったのが今回のきっかけです。

※今回の話はVIVEトラッカー(2018)でしか動作確認していないので旧VIVEトラッカーでは動かない可能性があります。
# VIVEトラッカー
VRChatでフルボディトラッキングをするために使うVIVEトラッカーですが、電源ボタンとPOGO pinを利用して5つまでのボタン入力を扱うことができます。
しかし、現在のSteamVRのバージョンではコントローラの入力を左右の手で行うことしか想定されていないため、3台目以降のコントローラ及びトラッカーのボタン入力を取得することができません。
ではどうするのかというと、トラッカーのHID Inputレポートからボタン入力を取得します。
# 実装
HIDの実装には[hidapi](https://github.com/signal11/hidapi)を利用します。また、VIVEコントローラで[似たようなこと](https://github.com/jb55/libvive/blob/master/vivehid.c)をしている人がいたので参考にしました。

まず、トラッカー（正確にはトラッカーとペアリングされたドングル）のInputレポートを読み取るためには、トラッカーのベンダーIDとプロダクトIDとシリアル番号を知る必要があります。トラッカーのHID情報はhidpaiのhid_enumerateという関数を利用して知ることができます。

```cpp
#include <stdio.h>
#include "hidapi.h"

int main(int argc, char* argv[]){
  //Enumerate and print the HID devices on the system
  struct hid_device_info *devs, *cur_dev;

  devs = hid_enumerate(0x0, 0x0);
  cur_dev = devs;
  while (1) {
    fprintf(stderr, "Device Found\n  type: %04hx %04hx\n  path: %s\n  serial_number: %ls",
      cur_dev->vendor_id, cur_dev->product_id, cur_dev->path, cur_dev->serial_number);
    fprintf(stderr, "\n");
    fprintf(stderr, "  Manufacturer: %ls\n", cur_dev->manufacturer_string);
    fprintf(stderr, "  Product:      %ls\n", cur_dev->product_string);
    fprintf(stderr, "\n");
    if (cur_dev->next) {
      cur_dev = cur_dev->next;
    }
    else {
      break;
    }
  }
  hid_free_enumeration(devs);
  return 0;
}
```

これを実行すると、トラッカのベンダーIDは0x28deで、プロダクトIDは0x2101であり、トラッカーごとに別々のシリアル番号があるということが分かりますが、VIVEコントローラのベンダーIDとプロダクトIDも同じ値なので、注意が必要です。

トラッカーのInputレポートには様々な情報が含まれますが、ボタン入力を取得するためにはInputレポートの4バイト目と6バイト目のデータを見る必要があります。

|4バイト目の値|6バイト目の値|ボタン|
|:---:|:---:|:--:|
|0x20|0x01|Trigger|
|0x20|0x04|Pad|
|0x20|0x08|Power|
|0x20|0x10|Grip|
|0x20|0x20|Menu|

ボタン入力があったとき4バイト目の値が0x20になり、6バイト目のデータからどのボタンが押されたかが分かります。また、ボタンを離したときも、4バイト目の値が0x20になり、6バイト目の押していたボタンに対応するビット値が0になります。

以上のことを踏まえて、トラッカーのボタンでGeForce Experienceを利用した録画するためのコードが以下のようになります。

```cpp
#include <stdio.h>
#include <windows.h>
#include "hidapi.h"

#define VID 0x28de
#define PID 0x2101

wchar_t serial_number[] = L"3A6F5B0637"

int main(int argc, char* argv[]){
  int res;
  unsigned char buf[128] = { 0 };
  hid_device *handle;

  // Open the device
  handle = hid_open(VID, PID, serial_number);

  if (!handle) {
    fprintf(stderr, "Couldn't get hid handle\n");
    exit(1);
  }
	
  while (1) {
    res = hid_read(handle, buf, 128);
    if (!res) continue;
    if (buf[4] == 0x20) {
      if ((buf[6] & 0x08) == 0x08){
        fprintf(stderr, "power\n");
        MessageBeep(MB_ICONINFORMATION);
        keybd_event(VK_LMENU, 0x00, KEYEVENTF_EXTENDEDKEY | 0, 0);	//ALT
        keybd_event(VK_F9, 0x00, KEYEVENTF_EXTENDEDKEY | 0, 0);	//F9
        keybd_event(VK_F9, 0x00, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
        keybd_event(VK_LMENU, 0x00, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
        Sleep(100);	//Chattering prevention
        }
    }
  }
  return 0;
}
```

GeForce Experienceで録画するためにはキーボードのALTとF9を同時に入力する必要があるのでkeybd_eventという関数を使っています。また、HMDをかぶっていると録画が開始されたかわからないので、ボタンが押されたときに音を出すようにしています。ボタンのチャタリングの影響で同じInputレポートを何回も送信することがあるので、100msの待ち時間を追加しています。
# おわりに
トラッカーのHID Imputレポートを読み取ることで、簡単にボタン入力を取得することができました、プログラムを工夫することで、ボタンの長押しやダブルクリックにも対応できそうです。今回の目的以外にも結構応用できそうなので、是非試してみてください。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">トラッカーの電源ボタンで録画出きるようになった<a href="https://twitter.com/hashtag/VRChat?src=hash&amp;ref_src=twsrc%5Etfw">#VRChat</a> <a href="https://t.co/zpoQT4PwFB">pic.twitter.com/zpoQT4PwFB</a></p>&mdash; mako@VRC (@mako_vr) <a href="https://twitter.com/mako_vr/status/1117763740206649345?ref_src=twsrc%5Etfw">2019年4月15日</a></blockquote>