プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

Razer Tartarus V2をRazer synapse3無しで動かす

 昨日不満をぶちまけたRazer Tartarus V2だが、悪いのはソフトであって、ハードではない。
 ソフトを憎み、ハードを憎まず。


 ハードは物がなければどうにもならないが、ソフトなら何とかならないこともないはずだ。

 

HID macros

 ここでまず候補に挙がるのが、HID macrosだろう。(※AutoHotkeyLowLevelKeyboardProcによるフックで動くので、デバイスごとの設定はできない。)

 だが、ここで問題が。私のWindows 10ではHID macrosが動かないのだ。

 もう開発されていないし、メンテナンスもされていないので仕方がないが、何故だ。

 

HidKeySequence

 HidKeySequenceという複数のデバイスを認識し、それぞれにマクロを割り当てるソフトがあるらしい。
 これだ!と勇み足でダウンロードボタンを押すが、私がアクセスブロックされて、ダウンロード出来ない。何故だ。

 私は何か悪さをしたのだろうか。
 人から拒否されるなんて、そんな…思い当たる節が多すぎるが、HidKeySequenceは昨日知ったのでまだ悪さはしてないと信じたい。

 
 

 公式はウンコ。AutoHotkeyは駄目。HID macrosは動かない。HidKeySequenceは拒否された。
 残念だ。四面楚歌だ。ここで試合終了です。

 

 

パンがないならパンを焼けば良いじゃない

 と、普通ならもう諦めて、大人しくTartarus V2(1万円)をそっとゴミ箱に捨てるか、Razer synapse3と共に生きる覚悟を決めるのだろうが、残念だが私は糞ニートだ。
 ここで我を通せぬ様では糞ニートなんてやっていられない。こんなところで折れる様では、老後が心配になってしまうではないか。親をも恐れぬ精神で立ち向かう義務がある。

 さて、長い前置きはここまでにして、もうこうなってしまった以上、自分でプログラムするしかない。
 が、ここでも問題がある。

 Windowsにおいて、キーボード入力をフックするにはLowLevelKeyboardProcを使えばいいのだが、これだとAutoHotkeyと同様でデバイスを認識出来ない。デバイスを認識したいならRaw Inputを使う必要がある。

 なら、これら二つを組み合わせれば良い。これは完全に正しい。これらの情報のどちらが先に来るのか分からない、という点を除けば。

 正確に認識するには入力をバッファリングやブロックしたりと、かなり面倒だ。

 

 だったら、もう直接デバイスの入力を奪ってしまえば良いのだが、これをするにはドライバを実装する必要がある。
 これも面倒だ。

 

Interception

 流石にドライバは書きたくないし、WinAPIも叩きたくない。
 どうにかならないかと探していたら、Interception(GitHub)という、マウスやキーボードの入力を傍受し、書き換えたり送信したりが簡単に出来るCライブラリを見つけた。

 仕組みとしてはInterceptionが提供する仮想ドライバが各マウスやキーボードの入力を制御する機能を提供し、ライブラリ利用者はその仮想ドライバを操作する。

 なお、ドライバは商用版ではソースコードが提供される様だが、フリー版では非公開だ。
 ドライバが安全かどうかは信じて使うしかない。暫く通信を監視してたけど、怪しげな通信は無かったと思うよ。(もし、この記事を真似て自作する場合は、自己責任でお願いします。)


 

 さて、先ずはRazer Tartarus V2の入力をすべて奪ってみる。(なお、HIDは環境に合わせて変更してしてください。)

#include "interception.h"
#include <windows.h>
#include <iostream>
#include <wchar.h>
using namespace std;

//環境に合わせて変更してください。
const wchar_t KEYBD_HID[] = L"HID\\VID_1532&PID_022B&REV_0200&MI_00";
const wchar_t MOUSE_HID[] = L"HID\\VID_1532&PID_022B&REV_0200&MI_02";

int main(){
  // 高優先度化
  SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);

  //Interceptionのインスタンス生成
  auto context = interception_create_context();
  if(!context)return 1;
  interception_set_filter(context, interception_is_keyboard, 
                          INTERCEPTION_FILTER_KEY_DOWN | 
                          INTERCEPTION_FILTER_KEY_UP |
                          INTERCEPTION_KEY_E0|
                          INTERCEPTION_KEY_E1);
  interception_set_filter(context, interception_is_mouse,
                          INTERCEPTION_MOUSE_WHEEL|
                          INTERCEPTION_FILTER_MOUSE_MIDDLE_BUTTON_DOWN|
                          INTERCEPTION_FILTER_MOUSE_MIDDLE_BUTTON_UP
                          );


  //  Tartarus V2を探す
  InterceptionDevice keyboard=INTERCEPTION_MAX_DEVICE,mouse=INTERCEPTION_MAX_DEVICE;
  wchar_t buf[500];
  for(size_t i = 0;i<INTERCEPTION_MAX_KEYBOARD;i++){
    InterceptionDevice d = INTERCEPTION_KEYBOARD(i);
    if(interception_get_hardware_id(context, d,buf, sizeof(buf)) && 
       wcscmp(KEYBD_HID,buf)==0){
      keyboard = d;
      break;
    }
  }
  for(InterceptionDevice i = 0;i<INTERCEPTION_MAX_MOUSE;i++){
    InterceptionDevice d = INTERCEPTION_MOUSE(i);
    if(interception_get_hardware_id(context, d, buf,sizeof(buf)) &&
       wcscmp(MOUSE_HID,buf)==0){
      mouse = d;
      break;
    }
  }
  if(keyboard == INTERCEPTION_MAX_DEVICE || mouse ==INTERCEPTION_MAX_DEVICE){
    interception_destroy_context(context);
    return 1;
  }
  
  // 入力を処理する
  InterceptionDevice device;
  InterceptionStroke stroke;
  while(interception_receive(context, device = interception_wait(context), &stroke, 1)>0){
    if(device == keyboard){
      InterceptionKeyStroke& s = *(InterceptionKeyStroke *)&stroke;
      cout << "Keyboard Input "
           << "ScanCode="<< s.code 
           << " State="<< s.state<<endl;
    }else if(device == mouse){
      InterceptionMouseStroke& s = *(InterceptionMouseStroke *)&stroke;
      cout << "Mouse Input" 
           << " State="<<s.state
           << " Rolling="<<s.rolling
           << " Flags="<<s.flags
           << " (x,y)=("<<s.x<<","<<s.y<<")"
           << endl;
    }else{
      //他のデバイスの入力は通過させる
      interception_send(context, device, &stroke, 1);
      if(interception_is_keyboard(device)){//Escapeで終了
        InterceptionKeyStroke &s = *(InterceptionKeyStroke *) &stroke;
        if(s.code == 1) break;
      }
    }
  }

  interception_destroy_context(context);
  return 0;
}


 これを以下のコマンドでコンパイル。

g++ main.cpp -linterception -Ldllのあるディレクトリ

 生成されたa.exeを実行してみる。(dllは読み込める様にしておくこと)
f:id:nodamushi:20180617154501p:plain

 おぉ、Tartarus V2の入力部分は、「Keyboard Input~~」、「Mouse Input~~」と出るが、他のキーボードで「hokanoki-bo-do(他のキーボード)」と打ったところはそのまま出力されている。
 確かにTartarusの入力を認識し、その入力を奪うことが出来た様だ。

 入力が奪えたなら、今度は入力を変更してみる。Tartarusの1キーが押されたら「nodamusi」、2キーが押されたら「Ctrl+Z×3」を入力する様にしてみる。
if(device==keyboard)の中に以下を追加。

      if(s.code == 2) {
        // 1キー
        s.code = 49;//n
        interception_send(context, device, &stroke, 1);
        s.code = 24;//o
        interception_send(context, device, &stroke, 1);
        s.code = 32;//d
        interception_send(context, device, &stroke, 1);
        s.code = 30;//a
        interception_send(context, device, &stroke, 1);
        s.code = 50;//m
        interception_send(context, device, &stroke, 1);
        s.code = 22;//u
        interception_send(context, device, &stroke, 1);
        s.code = 31;//s
        interception_send(context, device, &stroke, 1);
        s.code = 23;//i
        interception_send(context, device, &stroke, 1);
      }else if(s.code == 3){
        // 2キー
        s.code = 0x1D;//CTRL
        interception_send(context, device, &stroke, 1);
        s.code = 44;// z
        interception_send(context, device, &stroke, 1);
        if(s.state == 0){//キーを押した場合は更に追加で2回押す
          s.code = 0x1D;//CTRL
          interception_send(context, device, &stroke, 1);
          s.code = 44;// z
          interception_send(context, device, &stroke, 1);
          s.code = 0x1D;//CTRL
          interception_send(context, device, &stroke, 1);
          s.code = 44;// z
          interception_send(context, device, &stroke, 1);
        }
      }

 で、試しに実行してみる。(1を押す→Enter→1を押す→2を押す)
f:id:nodamushi:20180617155946p:plain

 おぉおお!
 入力を奪えて、異なる入力を突っ込むことが出来た。もう何も怖くない。

 ちなみに、このプログラムのメモリ使用量は600kbだ。-O3 -Wl,--subsystem,windows -mwindowsをオプションに入れてコンパイルすると300kb程度になった。ドライバがどの程度のメモリを使ってるか分からないが、Razer synapse3の360Mbと比べるべくもないだろう。変な情報も送信しないし。

 むろん、よく出来たソフトに比べれば、直にCを書くのは面倒くさい。ソフトごとにキーバインドを変えたければ、それように作り、手動で起動、終了をするか、アプリケーションを自動認識する機能を作る必要がある。ちょっと面倒だ。

 だが、ドライバを書いたりWindows APIを直に弄るより圧倒的に楽だし、C言語だからやりたい放題だ。
 例えば、他の矢印ボタンが押されてる間は、他の矢印ボタンは無効化とか、最初の矢印ボタンに変更なんてことも可能だ。


 とりあえず、これで快適なRazer Tartarus V2環境を手に入れることが出来た。
 Razer Tartarus V2は今、神となったのだ(`・д´・ )