2023-06-08 (Thu) [長年日記]

_ [ahk]ホイールのマネ2023

というか AutoHotkey 2 対応。関数ネストとかファットアローとか、まあまあ JS みたいに書ける。もう v1 で書く気にはならない。

あと、トラックボールが壊れたのでエレコム HUGE というやつに買いかえた。

(←Amazonプラグイン動作していないかも?)

これは中央ボタンよりも forward ボタンの方が押しやすいので、XButton2 をトリガーにして使っている。

#Requires AutoHotkey v2.0
#Warn
CoordMode("Mouse", "Screen")

; 設定 (グローバル変数は各関数内で変更不可能らしい?)
errortip := true ; 例外を ToolTip に表示したければ
swipelike := true ; スクロールバードラッグよりスワイプに似せるなら
hidecursor := true ; 押しながら移動のときカーソル消去するなら
simclick := "LButton" ; 長押しではないとき、これを押したことにする
maxcount := 10 ; 一回のループあたりのホイール回数制限
; https://learn.microsoft.com/windows/win32/inputdev/wm-mousewheel
halfdelta := 60 ; なぜか知らんが WHEEL_DELTA の半分で1行になるっぽい
; 移動による加速の加減
moveaccel := (delta) => delta * Abs(delta) ; 適当に2乗でいいかな
; ホイール速度による加速の加減
; 適当に間隔700ms未満から加速 https://www.desmos.com/calculator/njyks9wsta
wheelaccel := (one) => one * (1 + (Max(0, 700 - A_TimeSincePriorHotkey) / 300) ** 3)

; 各種のウェイトをなしにする
SetKeyDelay(-1, -1)
SetMouseDelay(-1)
SetDefaultMouseSpeed(0)
SetWinDelay(-1)

; ボタンを押しながらマウス移動でホイール動作のふり
; XButton1(戻るボタン)は使うけど2(進む)って使わなくない?
*XButton2::
{
  ; 最初のモディファイア部分を消した純粋なボタン名
  thiskey := RegExReplace(A_ThisHotkey, "\W")
  ; エラー消す
  If (errortip)
    ToolTip

  MouseGetPos(&sx, &sy, &id, &ctrl, 1)
  If (ctrl == "")
    ctrl := WinGetClass("ahk_id " id)
  uwp := exceptional(ctrl, id)

  setcursor(false)

  moved := false
  sumdelta := 0
  While GetKeyState(thiskey, "P")
  {
    MouseGetPos(&x, &y)
    delta := swipelike ? y - sy : sy - y

    If (delta == 0)
      Continue

    If (!uwp)
    {
      PostMessage(0x20A,
        (moveaccel(delta) << 16) | mods(),
        (sy << 16) | sx,
        ctrl, "ahk_id " id)
    }
    Else
    {
      sumdelta += moveaccel(delta)
      wheelcount := Integer(sumdelta / halfdelta)
      If (wheelcount != 0)
      {
        SendInput("{Blind}{"
          . (wheelcount > 0 ? "WheelUp" : "WheelDown")
          . " " Min(maxcount, Abs(wheelcount))
          . "}")
        sumdelta := 0
      }
    }

    moved := true
    MouseMove(sx, sy, 0)
  }

  setcursor(true)

  ; 押してすぐ離した場合など
  If (!moved)
    SendInput("{Blind}{" . (simclick ? simclick : thiskey) . "}")

  Return

  ; カーソルを見せたり見せなかったりしろ
  setcursor(visible)
  {
    static AFF := Buffer(32 * 4, 0xFF)
    static X00 := Buffer(32 * 4, 0x00)
    ; カーソルは再利用できず、毎回作成しなければいけないみたい
    static nullcursor := () => DllCall("CreateCursor", "UInt", 0,
      "Int", 0, "Int", 0, "Int", 32, "Int", 32,
      "Ptr", AFF, "Ptr", X00)

    If (!hidecursor)
      Return

    If (visible)
      DllCall("SystemParametersInfo", "UInt", 0x57, "UInt", 0, "UInt", 0, "UInt", 0)
    Else
    {
      ; サイズ変更と砂時計系は変更しないでおく
      ; https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-setsystemcursor
      DllCall("SetSystemCursor", "Ptr", nullcursor(), "Int", 32512) ; ノーマル
      DllCall("SetSystemCursor", "Ptr", nullcursor(), "Int", 32513) ; 文字Iビーム
      DllCall("SetSystemCursor", "Ptr", nullcursor(), "Int", 32649) ; リンク上の指
    }
  }
}

; ホイールの加速
; cf. https://www6.atwiki.jp/eamat/pages/32.html
*WheelUp::
*WheelDown::
{
  ; 最初のモディファイア部分を消した純粋なボタン名
  thiskey := RegExReplace(A_ThisHotkey, "\W")

  MouseGetPos(&x, &y, &id, &ctrl, 1)
  If (ctrl == "")
    ctrl := WinGetClass("ahk_id " id)

  v := (thiskey == "WheelUp") ? 1 : -1
  If (A_PriorHotkey == A_ThisHotkey)
    v := wheelaccel(v)

  If (!exceptional(ctrl, id))
    PostMessage(0x20A,
      Integer(halfdelta * v) << 16 | mods(),
      (y << 16) | x,
      ctrl, "ahk_id " id)
  Else
    SendInput("{Blind}{" thiskey " " Abs(Integer(v)) "}")
}

; PostMessageが使えるかどうか確認
exceptional(class, id)
{
  static tttext := ""
  postable := true
  try
    PostMessage(0, , , class, "ahk_id " id)
  catch TargetError
  {
    postable := false
    If (errortip and !(tttext ~= " " class))
    {
      tttext .= " " class
      ToolTip("TargetError classes:" tttext)
    }
  }
  ; UWP?
  Return (class ~= "^Windows\.UI\.Core\.CoreWindow") or !postable
}

; Ctrl押しながらでズームとかできるように
mods()
{
  static modkeys := [
    "LButton",
    "RButton",
    "Shift",
    "Ctrl",
    "MButton",
    "XButton1",
    "XButton2"
  ]
  rv := 0
  For i, v in modkeys
    rv |= (1 << (i - 1)) * GetKeyState(v)
  Return rv
}

; おまけ: マウスカーソル直下ウィンドウの最大化トグル
AppsKey::
{
  MouseGetPos(, , &id)
  If (WinGetMinMax("ahk_id " id))
    WinRestore("ahk_id " id)
  Else
    WinMaximize("ahk_id " id)
}

Fn1 ボタンを AppsKey にして、Fn2 を Alt+Esc にして使ってみている。アプリケーションによっては Ctrl+C と Ctrl+V にしても良さそう。


«前の日記(2023-06-06 (Tue)) 最新