GPD WIN用のパッド操作用AutoHotkeyスクリプト

# はじめに
[GPD WIN](https://www.google.co.jp/search?q=GPD+WIN)というデバイスが一部で人気です。
このデバイスには通常のポインティングデバイスはついていませんが、代わりにゲームパッドがついており、パッドのモードを切り替えることでマウスとしても使えます。
ですがデフォルトの挙動やボタン設定がしっくりこないので、AutoHotkeyでスクリプトを書きました。

パッドをポインティングデバイスにする方法としては、[JoyToKey](http://joytokey.net/)を使う方法が一般的ですが、JoyToKeyはシェアウェア[^1]なのと、AutoHotkeyのほうが細かい挙動がいじりやすいのでAutoHotkeyを使用しています。

[^1]: 試用でも機能制限は無いけどお金払うつもりがないのに使い続けるのもアレ


# ボタン配置(暫定)
とりあえず以下のように設定。使ってくうちにより使いやすい配置にしてきます。
L3/R3には無変換/変換を割り当てて、IMEのOFF/ONに使っています。
Start/Selectは押しづらいので未設定です。コピペとか割り当てると良いかも。

クリック(A,Bボタン)を押したときに、マウスポインタを一瞬停止するようにしています。これはスティックを操作しながらボタンを押したときに、予期せぬドラッグ操作になることを防ぐためです。この辺の挙動を自由にいじれるのがJoyToKeyではなくAutoHotkeyを使うメリットです。

| ボタン | 機能 |
| ---- | ---- |
| 左スティック | ポインタの移動 |
| 右スティック上下 | スクロール |
| 右スティック左右 | Shift+Tab/Tab |
| 方向ボタン | 矢印キー |
| Aボタン | 左クリック |
| Bボタン | 右クリック |
| Xボタン | タスクビュー[^2] |
| Yボタン | 中クリック |
| L1ボタン | スローボタン<br />(押してる間ポインタの動きが遅くなる) |
| R1ボタン | 修飾キー |
| L2/R2トリガー | 戻る/進む |
| L3/R3 | 無変換/変換 |
| R1+Aボタン | Enter |
| R1+Bボタン | Esc |
| R1+Xボタン | Alt+Tab |
| R1+Yボタン | Win |
| Win+J | スクリプトのON/OFF |

[^2]: Window10でWin+Tab押すと出てくるやつ

# スクリプト
AutoHotkeyではキーラベルにスティックが使えません。GetKeyStateで状態は取得できるので、Loopで常に監視しています。


```ahk:GPDWINPad.ahk
; GPD WIN用スクリプト
; ***XInputモードで使用のこと。***

#SingleInstance, FORCE
SetMouseDelay, 0

DeadMargin := 1        ; スティックの不感帯
SpeedNormal := 0.4     ; 通常時のマウスポインタ移動速度
SpeedSlow := 0.08      ; スローボタンを押したときのマウスポインタ移動速度
WheelWaitNormal := 50  ; 右スティックでのスクロール間隔
WheelWaitSlow := 250   ; スローボタンを押したときの右スティックでのスクロール間隔
SlowButton := "Joy5"   ; ポインタの移動やスクロールを遅くするボタン
LClickButton := "Joy1" ; 左クリックに設定するボタン
RClickButton := "Joy2" ; 右クリックに設定するボタン
ShiftButton := "Joy6"  ; 他のボタンの機能を変更する修飾ボタン
ButtonWait := 100      ; ボタン押下後に待つ場合の時間



isPause := 0
isLClickButtonDowned := 0
isRClickButtonDowned := 0
isL2ButtonDowned := 0
isR2ButtonDowned := 0
lastClick := A_TICKCOUNT
Gosub, JoyStickInit

Loop
{
	while isPause ;PauseがSuspendと組み合わせたときに挙動不審なのでフラグで管理
		sleep, 1
	
	Gosub, GetJoyStickState
;	ToolTip, X:%stateJoyX% Y:%stateJoyY% Z:%stateJoyZ% U:%stateJoyU% R:%stateJoyR% POV:%stateJoyPOV%
	
	; マウスカーソルの移動
	diffX := stateJoyX - defaultJoyX
	diffY := stateJoyY - defaultJoyY
	
	coef := GetKeyState(SlowButton,"P") ? SpeedSlow : SpeedNormal
	if(Abs(diffX) > DeadMargin && A_TICKCOUNT - lastClick > ButtonWait)
		x := diffX * coef
	if(Abs(diffY) > DeadMargin && A_TICKCOUNT - lastClick > ButtonWait)
		y := diffY * coef
	if(x || y)
	{
		MouseMove, %x%, %Y%,, R
		Sleep, 1
	}
	x := 0
	y := 0
	
	
	; スクロール
	diffR := stateJoyR - defaultJoyR
	if(Abs(diffR) > DeadMargin)
	{
		if(diffR < 0)
		{
			MouseClick, WheelUp
		}
		Else
		{
			MouseClick, WheelDown
		}
		wait := GetKeyState(SlowButton, "P") ? WheelWaitSlow : WheelWaitNormal
		Sleep, %wait%
	}
	
	
	diffU := stateJoyU - defaultJoyU
	if(diffU > 40){
		Send, {Tab}
		Sleep, %ButtonWait%
	}
	if(diffU < -40){
		Send, +{Tab}
		Sleep, %ButtonWait%
	}
	
	
	; L2, R2
	diffZ := stateJoyZ - defaultJoyZ
	if(diffZ > 40 && !isL2ButtonDowned){ ;L2 Down
		isL2ButtonDowned := 1
		Send, {Browser_Back}
	}
	if(diffZ < -40 && !isR2ButtonDowned){ ;R2 Down
		isR2ButtonDowned :=1
		Send, {Browser_Forward}
	}
	if(-10 < diffZ && diffZ < 10 && isL2ButtonDowned) ;L2 Up
		isL2ButtonDowned := 0
	if(-10 < diffZ && diffZ < 10 && isR2ButtonDowned) ;R2 Up
		isR2ButtonDowned := 0
	
	
	; 方向ボタン
	if(stateJoyPOV != -1)
	{
		if(stateJoyPOV == 0)
			Send, {Up}
		if(stateJoyPOV == 9000)
			Send, {Right}
		if(stateJoyPOV == 18000)
			Send, {Down}
		if(stateJoyPOV == 27000)
			Send, {Left}
		Sleep, %ButtonWait%
	}

	; 左クリック
	; Joy1 Upで拾えないのでLoop中に記述
	If(GetKeyState(LClickButton, "P"))
	{
		If(ShiftButton())
		{
			Send, {Enter Down}
			while GetKeyState(LClickButton, "P")
				Sleep, 1
			Send, {Enter Up}
		}
		else
		{
			If isLClickButtonDowned = 0
			{
				Click, LEFT, D
				isLClickButtonDowned = 1
				lastClick = %A_TICKCOUNT%
			}
		}
	}
	else
	{	
		If isLClickButtonDowned = 1
		{
			Click, LEFT, U
			isLClickButtonDowned = 0
		}
	}
	
	; 右クリック
	If(GetKeyState(RClickButton, "P"))
	{
		if(ShiftButton())
		{
			Send, {Esc Down}
			while GetKeyState(RClickButton, "P")
				Sleep, 1
			Send, {Esc Up}
		}
		else
		{
			If isRClickButtonDowned = 0
			{
				Click, RIGHT, D
				isRClickButtonDowned = 1
			}
		}
	}
	else
	{	
		If isRClickButtonDowned = 1
		{
			Click, RIGHT, U
			isRClickButtonDowned = 0
		}
	}

}
return


JoyStickInit:
	Gosub, GetJoyStickState
	
	defaultJoyX := stateJoyX
	defaultJoyY := stateJoyY
	defaultJoyZ := stateJoyZ
	defaultJoyU := stateJoyU
	defaultJoyR := stateJoyR
Return

GetJoyStickState:
	GetKeyState, stateJoyX, JoyX, P
	GetKeyState, stateJoyY, JoyY, P
	GetKeyState, stateJoyZ, JoyZ, P
	GetKeyState, stateJoyU, JoyU, P
	GetKeyState, stateJoyR, JoyR, P
	GetKeyState, stateJoyPOV, JoyPOV, P
Return

ShiftButton()
{
	global ShiftButton
	Return, GetKeyState(ShiftButton, "P")
}



Joy3::
	if(ShiftButton())
	{
		Send, !{Tab}
	}
	else
	{
		IfWinActive, ahk_class MultitaskingViewFrame
			Send, {Enter}
		Else
			Send, #{Tab}
	}
Return
Joy4::
	if(ShiftButton())
	{
		Send, {LWin}
	}
	else
	{
		IfWinActive, ahk_class MultitaskingViewFrame
			Send, {Delete}
		Else
			Click, Middle
			Sleep, %ButtonWait%
	}
Return
; Start
Joy8::Gosub, JoyStickInit ; スティック初期化
; L3
Joy9::vk1Dsc07B ; 無変換
; R3
Joy10::vk1Csc079 ; 変換



; スクリプト一時停止トグル
#J::
	Suspend
	isPause := isPause ? 0 : 1
Return

```

行き当たりばったりで書いたので汚いコードだけど動くから良いよね！