概要
Raspberry Piでゲームパッドの入力をキーボードやマウスの入力に変換するソフトは今の所無いようなので、node.jsでさくっと作ってみた。
(Intel/AMD製CPU用の)Linux・Windows・MacにはGUIで設定できる同様のソフトが存在するので素直にそっちを使ったほうがいいです。
インストール
node.js及び、joystick・robotjsモジュールをインストールする必要があります。
また、robotjsをインストールするためにlibx11-devとlibxtst-devもインストールする必要があるようです(参考)
sudo apt install npm libx11-dev libxtst-dev
npm install joystick robotjs --save-dev
スクリプト
joystickモジュールでゲームパッドの入力を受け取ってその結果を元にrobotjsモジュールを利用してキー・マウス操作する感じになります。
接続されるまでポーリングで待機したり、キー・マウス入力への変換をオブジェクトで定義したりできるようにしてみました。
joykey.js
constfs=require('fs');constjoystick=require('joystick');constrobot=require('robotjs');varjoys=newjoystick(jid);//コンソールにジョイスティックイベントの内容を表示//var consolelog = true;varconsolelog=false;//ジョイスティックのID(通常は0)varjid=0;//キーやスティックが押されたときの動作を定義/*
ジョイスティックのボタン押下(button)
"0", "1", "2"....
ジョイスティックのスティック操作(axis)
"a0", "a1", "a2"...
type: "enable" キー変換の有効・無効の切り替え(無効でもenableだけは有効)
type: "key" キークリック
key: クリックするキー文字もしくはキー文字列
type: "text" 文字列送信
str: 送信する文字列
type: "mouse" マウスボタン操作
mtype: マウスボタンの操作タイプ("ckick", "toggle", "double")
button: クリックするマウスボタン("left", "middle", "right")
type: "move" マウス移動
mx: x軸移動量
my: y軸移動量
type: "scroll" スクロール
mx: x軸スクロール量
my: y軸スクロール量
type: "updown" 上下カーソルキー
mv: 0以上:そのまま 0未満:反転
type: "leftright" 左右カーソルキー
mv: 0以上:そのまま 0未満:反転
*/varjoytable={"0":{type:"mouse",mtype:"click",button:"right"},"1":{type:"mouse",mtype:"click",button:"left"},"2":{type:"toggle",key:"left"},"a0":{type:"move",mx:10,my:0},"a1":{type:"move",mx:0,my:10},"a3":{type:"scroll",mx:0,my:5},"a4":{type:"leftright",mv:0},"a5":{type:"updown",mv:0},};//toggle用にマウスボタンの状態を記憶(trueが押されている状態)varmousestate={"left":false,"right":false,"middle":false};//キーマウス動作が有効かどうかvarenable=true;//定義を元にキーやマウスの動作を実行varsendkey=function(dat,mul){if(dat==null||dat.type==null)return;mul=mul<0?-1:1;if(dat.type==="enable"){//キー変換有効無効の切り替えenable=enable?false:true;}elseif(enable&&dat.type==="key"){//キー送信if(dat.key==null)return;robot.keyTap(dat.key);}elseif(enable&&dat.type==="text"){//文字列送信if(dat.str==null)return;robot.typeString(dat.str);}elseif(enable&&dat.type==="mouse"){if(dat.mtype==="click"){//マウスボタンクリックif(dat.button==null)return;robot.mouseClick(dat.button);}elseif(dat.mtype==="double"){//マウスボタンダブルクリックif(dat.button==null)return;robot.mouseClick(dat.button,true);}elseif(dat.mtype==="toggle"){//マウスボタン押下状態切り替えif(dat.button==null||mousestate[dat.button]==null)return;robot.mouseToggle(mousestate[dat.button]?"up":"down",dat.button);mousestate[dat.button]=mousestate[dat.button]?false:true;}}elseif(enable&&dat.type==="move"){//マウスポインタ移動if(dat.mx==null||dat.my==null)return;varpos=robot.getMousePos();robot.moveMouseSmooth(pos.x+(dat.mx*mul),pos.y+(dat.my*mul));}elseif(enable&&dat.type==="scroll"){//スクロールif(dat.mx==null||dat.my==null)return;robot.scrollMouse(dat.mx*mul,dat.my*mul);}elseif(enable&&dat.type==="updown"){//上下カーソルキー送信if(dat.mv==null)return;dat.mv=dat.mv<0?-1:1;robot.keyTap(dat.mv*mul<0?"up":"down");}elseif(enable&&dat.type==="leftright"){//左右カーソルキー送信if(dat.mv==null)return;dat.mv=dat.mv<0?-1:1;robot.keyTap(dat.mv*mul<0?"left":"right");}};//ジョイスティック接続待ち用のsleepvarsleep=function(waitSec){returnnewPromise(function(resolve){setTimeout(function(){resolve()},waitSec);});};//joystickイベントの定義varinit=function(joy){//ボタンクリックイベントjoy.on('button',button=>{if(consolelog)console.log({button});if(button.value!=0){sendkey(joytable[button.number],0);}});//axis移動イベントjoy.on('axis',axis=>{if(consolelog)console.log({axis});if(axis.value!=0){sendkey(joytable["a"+axis.number],axis.value);}});//エラーイベントjoy.on('error',async(err)=>{if(consolelog)console.log({err});//エラーが発生したら/dev/input/js?の存在を1秒ごとに確認して存在すれば再度joystickオブジェクトを作成while(1){if(fs.existsSync('/dev/input/js'+jid))break;awaitsleep(1000);}joys=newjoystick(jid);init(joys)});};//開始init(joys);
キーリピート対応版
このまんまだとキーリピートができないのでちょっと使いにくいが、対応しようと思うと根本的に作り直す感じになるっぽいので保留・・・
こっちの方もさくっと作ってみた。
joykeyrepeat.js
constfs=require('fs');constjoystick=require('joystick');constrobot=require('robotjs');varjoys=newjoystick(jid);//コンソールにジョイスティックイベントの内容を表示//var consolelog = true;varconsolelog=false;//ジョイスティックのID(通常は0)varjid=0;//キーやスティックが押されたときの動作を定義/*
ジョイスティックのボタン押下(button)
"0", "1", "2"....
ジョイスティックのスティック操作(axis)
"a0", "a1", "a2"...
type: "enable" キー変換の有効・無効の切り替え(無効でもenableだけは有効)
type: "key" キークリック
key: クリックするキー文字もしくはキー文字列
repeat: キーリピート(true:有効, false:無効)
type: "text" 文字列送信
str: 送信する文字列
repeat: キーリピート(true:有効, false:無効)
type: "mouse" マウスボタン操作
mtype: マウスボタンの操作タイプ("ckick", "toggle", "double")
button: クリックするマウスボタン("left", "middle", "right")
repeat: キーリピート(true:有効, false:無効)
type: "move" マウス移動
mx: x軸移動量
my: y軸移動量
repeat: キーリピート(true:有効, false:無効)
type: "scroll" スクロール
mx: x軸スクロール量
my: y軸スクロール量
repeat: キーリピート(true:有効, false:無効)
type: "updown" 上下カーソルキー
mv: 0以上:そのまま 0未満:反転
repeat: キーリピート(true:有効, false:無効)
type: "leftright" 左右カーソルキー
mv: 0以上:そのまま 0未満:反転
repeat: キーリピート(true:有効, false:無効)
*/varjoytable={"0":{type:"mouse",mtype:"click",button:"right",repeat:false},"1":{type:"mouse",mtype:"click",button:"left",repeat:false},"2":{type:"toggle",key:"left",repeat:false},"a0":{type:"move",mx:10,my:0,repeat:true},"a1":{type:"move",mx:0,my:10,repeat:true},"a3":{type:"scroll",mx:0,my:5,repeat:true},"a4":{type:"leftright",mv:0,repeat:true},"a5":{type:"updown",mv:0,repeat:true},};//ジョイスティックの状態を記憶varjoystate={"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"a0":0,"a1":0,"a2":0,"a3":0,"a4":0,"a5":0,"a6":0,"a7":0,"a8":0,"a9":0,};varjoyenable=false;//toggle用にマウスボタンの状態を記憶(trueが押されている状態)varmousestate={"left":false,"right":false,"middle":false};//キーマウス動作が有効かどうかvarenable=true;//定義を元にキーやマウスの動作を実行varsendkey=function(dat,mul){if(dat==null||dat.type==null)return;mul=mul<0?-1:1;if(dat.type==="enable"){//キー変換有効無効の切り替えenable=enable?false:true;}elseif(enable&&dat.type==="key"){//キー送信if(dat.key==null)return;robot.keyTap(dat.key);}elseif(enable&&dat.type==="text"){//文字列送信if(dat.str==null)return;robot.typeString(dat.str);}elseif(enable&&dat.type==="mouse"){if(dat.mtype==="click"){//マウスボタンクリックif(dat.button==null)return;robot.mouseClick(dat.button);}elseif(dat.mtype==="double"){//マウスボタンダブルクリックif(dat.button==null)return;robot.mouseClick(dat.button,true);}elseif(dat.mtype==="toggle"){//マウスボタン押下状態切り替えif(dat.button==null||mousestate[dat.button]==null)return;robot.mouseToggle(mousestate[dat.button]?"up":"down",dat.button);mousestate[dat.button]=mousestate[dat.button]?false:true;}}elseif(enable&&dat.type==="move"){//マウスポインタ移動if(dat.mx==null||dat.my==null)return;varpos=robot.getMousePos();robot.moveMouseSmooth(pos.x+(dat.mx*mul),pos.y+(dat.my*mul));}elseif(enable&&dat.type==="scroll"){//スクロールif(dat.mx==null||dat.my==null)return;robot.scrollMouse(dat.mx*mul,dat.my*mul);}elseif(enable&&dat.type==="updown"){//上下カーソルキー送信if(dat.mv==null)return;dat.mv=dat.mv<0?-1:1;robot.keyTap(dat.mv*mul<0?"up":"down");}elseif(enable&&dat.type==="leftright"){//左右カーソルキー送信if(dat.mv==null)return;dat.mv=dat.mv<0?-1:1;robot.keyTap(dat.mv*mul<0?"left":"right");}};//ジョイスティック接続待ち、ループ用のsleepvarsleep=function(waitSec){returnnewPromise(function(resolve){setTimeout(function(){resolve()},waitSec);});};//joystickイベントの定義varinit=function(joy){//ボタンクリックイベントjoy.on('button',button=>{if(consolelog)console.log({button});joystate[button.number]=button.value;});//axis移動イベントjoy.on('axis',axis=>{if(consolelog)console.log({axis});joystate['a'+axis.number]=axis.value;});//エラーイベントjoy.on('error',async(err)=>{if(consolelog)console.log({err});//エラーが発生したら/dev/input/js?の存在を1秒ごとに確認して存在すれば再度joystickオブジェクトを作成joyenable=false;while(1){if(fs.existsSync('/dev/input/js'+jid))break;awaitsleep(1000);}joys=newjoystick(jid);init(joys)});joyenable=true;};//ループvarstart=asyncfunction(){while(1){if(joyenable){for(varkeyinjoystate){if(joystate[key]!=0){sendkey(joytable[key],joystate[key]);if(!joytable[key].repeat)joystate[key]=0;}}}awaitsleep(16);}}//開始init(joys);start();