3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AutoHotKey V2 による HHKB Studio のカスタマイズ

Last updated at Posted at 2023-12-02

概要

AutoHotKey v2 を使って HHKB Studio (日本語配列) をカスタマイズした内容の備忘録です。
変換・無変換キー、マウスボタンへファンクションキーを割り当てて、それらに対してホットキーを割り当てています。
image.png

AutoHotKey のインストールとスクリプトの実行方法

公式サイトから "Latest Installer" を落としてインストールします。

この記事作成時点での作業環境は以下の通りです。

  • Windows 11 23H2
  • HHKB Studio B2.07 (日本語配列キーボードをUS配列の認識環境で利用)
  • AutoHotKey v2.0.10

V2は公開されてから日が浅いため、V1よりも情報が少ないですが、公式リファレンスを参考にコンフィグを作っていきます。

適当なテキストエディタを使って「script.ahk」というファイルを作成しました。
拡張子が関連付けされているので、スクリプトをそのままダブルクリックすると、AHKが起動してスクリプトを読み込んでくれるのですが、タスクマネージャなどの管理者権限で起動しているアプリに対しては、AHK自身も管理者権限で起動しないと入力を受け付けてくれません。このためスクリプトへのショートカットファイルを作成して、ファイルプロパティから管理者権限で起動する設定を行いました。

機能の有効化、AHKの操作

まずはAutoHotkey自体の設定と操作するホットキーを定義します。

機能の有効化、AHKの操作
#Requires AutoHotkey v2.0

#SingleInstance Force
#UseHook
InstallKeybdHook                ; キーボードフックの有効化
ProcessSetPriority "Realtime"   ; プロセスの優先順位

; AHKの操作
#SuspendExempt
^!s::Suspend    ; Ctrl+Alt+S - 一時停止
^!r::Reload     ; Ctrl+Alt+R - リロード
^!e::Edit       ; Ctrl+Alt+E - エディット
^!k::KeyHistory ; Ctrl+Alt+K - キーヒストリー
^!l::ListHotKeys ; Ctrl+Alt+L - ホットキーの一覧
^!Esc::ExitApp  ; Ctrl+Alt+Esc - AHK終了
#SuspendExempt False

カーソル操作を行うためのホットキー

無変換キーへ配置したF18キーを押している間は、ホームポジションキーに対してカーソル操作系のホットキーを割り当てます。
単体でタップした場合は AppsKey が送信されます。

; テキスト編集機能
F18::
{
    OutputDebug ThisHotkey
    inputKey := "F18"
    tapKey := "{AppsKey}"
    global SelectMode := 0

    KeyWait(inputKey)
    global SelectMode := 0
    
    if (IsSingleTap(ThisHotkey)) {
        Send(tapKey)
    }
}
#HotIf GetKeyState("F18", "P")
    q::!Left                        ; Back Page
    w::SelectMode_Send("^{Home}")   ; Head of Page
    e::SelectMode_Send("^{End}")    ; End of Page
    r::!Right                       ; Next Page
    t::return
    y::^y
    u:: global SelectMode += 1      ; Change SelectMode
    i::SelectMode_Send("{Home}")    ; Head of Line
    o::SelectMode_Send("{End}")     ; End of Line
    p::return

    a::SelectMode_Send("{PgUp}")
    s::SelectMode_Send("{Up}")
    d::SelectMode_Send("{Down}")
    f::SelectMode_Send("{PgDn}")
    g::Del
    h::BackSpace
    j::SelectMode_Send("^{Left}")   ; Left Word
    k::SelectMode_Send("{Left}")
    l::SelectMode_Send("{Right}")
    `;::SelectMode_Send("^{Right}") ; Right Word
   
    z::^z
    x::^x
    c::^c
    v::^v
    b::return
    n::return
    m::Enter
    ,::^BackSpace  ; Delete Left Word
    .::^Del        ; Delete Right Word
    /::return
#HotIf

ホットキー単体でタップされたかどうかを判定する関数。

; シングルタップの判定
IsSingleTap(thisHotkey, tappingTerm := TAPPING_TERM)
{
    if (A_ThisHotkey != thisHotkey) {
        return false ; 異なるホットキーが押された
    }
    else if (tappingTerm < 0) {
        return true
    }
    else if (A_TimeSinceThisHotkey > tappingTerm) {
        return false ; 時間のしきい値を超えた
    }
    else {
        return true
    }
}

カーソル移動キーに対して選択モードを切り替えるための関数。SelectMode が有効な場合は Shift による範囲選択または Shift+Ctrl+Alt による矩形選択が有効となります。

; カーソル範囲選択モード
SelectMode := 0
SelectMode_Send(keys)
{
    mod_key := ""
    if (SelectMode) {
        Switch Mod(SelectMode, 2)
        {
        Case 1:
            mod_key := "+"      ; Shift
        Default:
            mod_key := "+^!"    ; Shift+Ctrl+Alt
        }
    }
    Send mod_key keys
}

ウィンドウ操作機能

変換キーへ配置したF19のホットキーでは、OS機能のウィンドウスナップと、AutoHotkeyによるウィンドウのリサイズ機能を割り当てています。
単体でタップした場合は Win キーを送信します。

; ウィンドウ操作機能
*F19::
{
    OutputDebug ThisHotkey
    inputKey := "F19"
    tapKey := "{RWin}"
    global WinLock := true

    KeyWait(inputKey)
    WinLock := false
    
    if (IsSingleTap(ThisHotkey)) {
        Send(tapKey)
    }
}
#HotIf GetKeyState("F19", "P")
    ; Window Snap
    w::Send "#{Up}"
    e::Send "#{Down}"
    i::Send "#{Left}"
    o::Send "#{Right}"

    a::WinResize("a", 0, -A_ScreenHeight/4)
    s::WinResize("s", 0, -10)
    d::WinResize("d", 0,  10)
    f::WinResize("f", 0, A_ScreenHeight/4)
    j::WinResize("j", -A_ScreenWidth/4, 0)
    k::WinResize("k", -10, 0)
    l::WinResize("l",  10, 0)
    `;::WinResize(";", A_ScreenWidth/4, 0)
#HotIf

ウィンドウリサイズを行う関数

; ウィンドウをリサイズ
WinResize(inputKey, dw, dh)
{
    OutputDebug "WinResize " inputKey " " dw " " dh

    try {
        ; タスクバーの高さ(taskbarHeight)、アクティブなウィンドウタイトル(winTitle)を取得
        WinGetPos(,,, &taskbarHeight, "ahk_class Shell_TrayWnd")
        winTitle := WinGetTitle("A")

        ; ワンショットでリサイズした後、連続入力を受け付ける経過時間まで待機
        WinResize()
        KeyWait(inputKey, "T0.5")

        ; 連続リサイズ開始
        if GetKeyState(inputKey, "P") {
            SetTimer WinResize, 20
        }
        KeyWait(inputKey, "T1")

        ; リサイズ幅を大きくする
        if GetKeyState(inputKey, "P") {
            dw *= 2
            dh *= 2
        }
        KeyWait(inputKey)

        ; 画面縁に内接するようにウィンドウサイズを変更する
        WinResize()
        {
            WinGetPos &x, &y, &width, &height, winTitle
            width  := Min(width + dw, A_ScreenWidth)
            height := Min(height + dh, A_ScreenHeight - taskbarHeight)
            x := Min(x, A_ScreenWidth - width)
            y := Min(y, A_ScreenHeight - height - taskbarHeight)
            WinMove x, y, width, height, winTitle
        }
    }
    catch as e {
        OutputDebug " - catch as e.Message: " e.Message
    }

    SetTimer WinResize, 0
}

モディファイヤ状態をロックするための定義

モディファイヤのロック状態をグローバル変数に保存しておき、これらが true 状態のときにはモディファイヤキー付きでキー入力を行うための関数を定義。

; モディファイヤ状態を付加してキー送信
ShiftLock := false
CtrlLock := false
WinLock := false
pShiftLock := &ShiftLock
pCtrlLock := &CtrlLock
pWinLock := &WinLock
ModLock_Send(keys)
{
    ; モディファイヤキーのロック
    mod_key := (ShiftLock ? "+" : "") . (CtrlLock ? "^" : "") . (WinLock ? "#" : "")
    Send mod_key keys
}

"p"プレフィックスの付いた変数に、変数への参照を格納しておくことで、関数引数への参照渡しをglobal宣言せずに行うことができます。

; グローバル変数とその参照
ShiftLock := false
pShiftLock := &ShiftLock

; 宣言無しで参照演算子(&)を使うと、ローカル変数扱いとなるためエラー
a::Function(&ShiftLock)

; global宣言した後なら、グローバル変数への参照演算子(&)を使用可能
b::
{
    global ShiftLock
    Function(&ShiftLock)
}

; 演算子無しでグローバル変数を参照することは可能
c::Function(pShiftLock)

想定ローカル関数内の変数参照は、それが読み込まれるだけであれば、グローバル変数に解決される可能性があります。ただし、変数が代入や参照演算子(&)で使用された場合、デフォルトで自動的にローカルになります。これにより、関数は関数内で宣言することなく、グローバル変数の読み込みや、グローバル関数や組み込み関数の呼び出しを行うことができます。

主な文字入力キー全てに ModLock_Send でホットキーを設定します。

; モディファイヤロック
#HotIf ShiftLock or CtrlLock or WinLock
    1::
    2::
    3::
    4::
    5::
    6::
    7::
    8::
    9::
    0::ModLock_Send(A_ThisHotkey)

    q::
    w::
    e::
    r::
    t::
    y::
    u::
    i::
    o::
    p::ModLock_Send(A_ThisHotkey)

    a::
    s::
    d::
    f::
    g::
    h::
    j::
    k::
    l::
    `;::ModLock_Send(A_ThisHotkey)

    z::
    x::
    c::
    v::
    b::
    n::
    m::
    ,::
    .::
    /::ModLock_Send(A_ThisHotkey)

    -::
    =::
    \::
    [::
    ]::
    '::
    `::ModLock_Send(A_ThisHotkey)

    Tab::
    sc07D::
    sc02B::
    sc073::ModLock_Send("{" A_ThisHotkey "}")
#HotIf

左ボタン機能

左ボタンへ配置したF15キーには、ホールド中はShiftキー、タップやカーソル移動時は左クリックとして機能するホットキーを定義します。

; 左ボタン機能
+F15::+LButton
F15::DownLButton(ThisHotkey, "F15", pShiftLock)

; 左ボタンの操作
DownLButton(thisHotkey, inputKey, modLock)
{
    OutputDebug "DownLButton " thisHotkey
    static mouseBtn      := "LButton"
    static mouseBtn_Down := "{Blind}{LButton Down}"
    static mouseBtn_Up   := "{Blind}{LButton Up}"
    static mouseBtn_Tap  := "{Blind}{LButton}"
    
    ; modLockをホールド
    %modLock% := True

    ; inputKeyホールド中にカーソル移動したらマウスボタンを押下
    timeIdleMouse := A_TimeIdleMouse
    while GetKeyState(inputKey, "P") {
        if (A_TimeIdleMouse < timeIdleMouse) {
            Send(mouseBtn_Down)
            break
        }
        Sleep 0
    }
    KeyWait(inputKey)
    %modLock% := False
    
    ; マウスボタンの後処理
    if (GetKeyState(mouseBtn)) {
        ; マウスホールドを開放
        OutputDebug " - HoldUp " inputKey
        Send(mouseBtn_Up)
    }
    else if (IsSingleTap(thisHotkey, -1)) {
        ; マウスボタンのタップ
        OutputDebug " - Tap " inputKey
        Send(mouseBtn_Tap)
    }
}

マウス操作関連の機能を使うため、下記定義もスクリプト先頭に追加しておきます。

; マウス関連の設定
InstallMouseHook                ; マウスフックの有効化
CoordMode "Mouse", "Screen"     ; マウス座標系の基準

左ボタン(F15)+中ボタン(F16)の同時押しでウィンドウをカーソルで掴むように移動させます。

#HotIf GetKeyState("F15", "P")
    ; ウィンドウをカーソル移動に追従
    F16::
    {
        MouseGetPos &mx, &my, &wid
        winTitle := "ahk_id " wid
        WinGetPos &wx, &wy, &width, &height, winTitle
        dx := wx - mx
        dy := wy - my
        while GetKeyState("F16", "P") {
            MouseGetPos &mx, &my
            wx := dx + mx
            wy := dy + my
            WinMove wx, wy, width, height, winTitle
            Sleep 20
        }
    }
#HotIf

中ボタン機能

中ボタンへ配置したF16キーには、ホールド中はホイール操作、タップしたら中ボタンとして機能するホットキーを定義します。

; 中ボタン
F16::CursorToWheelMove(ThisHotkey, "F16", "MButton")

; カーソル移動によるホイール操作
CursorToWheelMove(thisHotkey, inputKey, tapKey)
{
    OutputDebug "CursorToWheelMove " thisHotkey
    static mousePerWheel := 20
    mouseMoved := false
    MouseGetPos &downX, &downY
    integralX := 0
    integralY := 0
    wheelX := 0
    wheelY := 0
    
    ; inputKeyホールド中にカーソル移動したらホイール操作
    while GetKeyState(inputKey, "P") {
        ; マウス移動量を積算
        MouseGetPos &mx, &my
        DllCall("SetCursorPos", "int", downX, "int", downY)
        integralX += mx - downX
        integralY += my - downY

        ; マウス移動量からホイール操作量を計算
        mouseMoved |= SendWheel(integralY, &wheelY, "WheelUp", "WheelDown")
        SendWheel(integralPos, &wheelPos, wheelBackward, wheelForward)
        {
            targetWp := integralPos // mousePerWheel
            if (targetWp = wheelPos) {
                return false
            }
            dw := Abs(targetWp - wheelPos)
            if (targetWp > wheelPos) {
                MouseClick wheelForward,,, dw
                wheelPos += dw
            }
            else {
                MouseClick wheelBackward,,, dw
                wheelPos += -dw
            }
            return true
        }
        Sleep 20
    }

    ; ボタンのタップ
    if (IsSingleTap(thisHotkey)) {
        Send("{" tapKey "}")
    }
}

中ボタン(F16)ホールド中のg h b n キーへホイール操作を割り当てています。

#HotIf GetKeyState("F16", "P")
    g::
    h::MouseClick "WheelUP",,, 1
    b::
    n::MouseClick "WheelDown",,, 1
#HotIf

右ボタン機能

右ボタンへ配置したF17キーには、ホールド中はシフトキーのホールド及び、マウスカーソル移動が低速になるように SystemParametersInfo を書き換える機能を割り当てました。
単体でタップした場合は、右ボタンが押下されます。

; 右ボタン
+F17::+RButton
F17::DownRButton(ThisHotkey, "F17", pShiftLock)

; 右ボタンの操作
DownRButton(thisHotkey, inputKey, modLock)
{
    OutputDebug "DownLButton " thisHotkey
    static mouseBtn_Down := "{Blind}{RButton Down}"
    static mouseBtn_Up   := "{Blind}{RButton Up}"
    static mouseBtn_Tap  := "{Blind}{RButton}"
    
    ; modLockをホールド
    %modLock% := True

    static SPI_GETMOUSESPEED := 0x70
    static SPI_SETMOUSESPEED := 0x71
    static OrigMouseSpeed := 0
    
    ; Retrieve the current speed so that it can be restored later:
    DllCall("SystemParametersInfo", "UInt", SPI_GETMOUSESPEED, "UInt", 0, "Ptr*", &OrigMouseSpeed, "UInt", 0)
    ; Now set the mouse to the slower speed specified in the next-to-last parameter (the range is 1-20, 10 is default):
    DllCall("SystemParametersInfo", "UInt", SPI_SETMOUSESPEED, "UInt", 0, "Ptr", 1, "UInt", 0)

    ; ホールド解除されるまで待機
    KeyWait(inputKey)
    %modLock% := False
    
    ; KeyWait inputKey  ; This prevents keyboard auto-repeat from doing the DllCall repeatedly.
    DllCall("SystemParametersInfo", "UInt", SPI_SETMOUSESPEED, "UInt", 0, "Ptr", OrigMouseSpeed, "UInt", 0)  ; Restore the original speed.

    if (IsSingleTap(thisHotkey))
    {
        ; マウスボタンのタップ
        OutputDebug " - Tap " inputKey
        Send(mouseBtn_Tap)
    }
}
3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?