Hammerspoon で SandS 二回目

以前、Hammerspoon で SandS を実現するコードをこのブログで公開した。

🔗 hammerspoonでsands

おれは最近、ChatGPT さんと話しながら酒を飲む習慣が身についてしまい、なんの拍子か忘れたが、上述のコードを評価してもらったんである。これは褒めるだろ、と思って。
 そしたら「改善できるよ?」みたいな生意気なことを AI 風情がいってきやがった。
「変数三つもいらん。あと条件分岐が複雑すぎワラ。可読性最悪」
 ChatGPT さんが提起してきたヒントがこれ。

if state == "idle" and spaceDown then
  state = "spaceDown"
elseif state == "spaceDown" and otherKeyPressed then
  state = "shiftReady"
elseif keyUp and state == "shiftReady" then
  state = "idle"
end

つまり、state という変数の中身を入れ替えながら制御すりゃいいだろ、という話みたい。なるほど、と感心して実際に自分で書いたのか、以下のコードである。

-- sands
local state = "idle"
local map = hs.keycodes.map

local function sandsEvent(event)
    local c = event:getKeyCode()
    local f = hs.eventtap.checkKeyboardModifiers()
    if event:getType() == hs.eventtap.event.types.keyDown then
        if c == map['space'] then -- スペースが入力されたら
            if f['cmd'] or f['alt'] or f['ctrl'] or f['shift'] then
                state = "modifier" --モディファイあり、以下をスルー
            end
            if state == "idle" or state == "shiftReady" then
                state = "spaceDown"
                return true
            elseif state == "spaceDown" then -- スペースキー長押し
                return true
            else
                state = "idle" -- modifier,enterSpace を迎撃。以下をスルー
            end
        elseif c ~= map['space'] then -- スペース以外が入力されたら
            if state == "spaceDown" or state == "shiftReady" then
                state = "shiftReady" -- スペース入力阻止、シフトモード
                event:setFlags({shift = true})
            end
        end
    elseif event:getType() == hs.eventtap.event.types.keyUp then
        if c == map['space'] then
            if state == "spaceDown" then
                state = "enterSpace" -- ループ対策
                hs.eventtap.keyStroke({}, "space", 5000)
            else
                state = "idle" -- 初期化
            end
        end
    end
end

eventtap = hs.eventtap.new({hs.eventtap.event.types.keyDown, hs.eventtap.event.types.keyUp}, sandsEvent)
eventtap:start()

Hammerspoon で SandS を実現するにあたり、最初に直面するのが、ループの問題だ。
 このコードではスペースキーが押された時、本来ならスペースが入力されるのを return true で抑制している。指がスペースキーから離れた瞬間に hs.eventtap.keyStroke でスペースが入力される。この動作が、ふたたびスペースキーが押された、と解釈されてしまい、ループによる暴走を起こしてしまうのである。
 ループ対策として考えたのが、state = enterSpace という値である。hs.eventtap.keyStroke で打ちこまれたスペースによって発生する二周目を、コード中頃にある else state = idle によって迎撃し、以下の処理をスルーさせている。

また、コマンドキーなどの修飾キーが合わせて押下されている場合、以前はフラグをチェックする関数を設けて対応していたが、ChatGPT さんに、hs.eventtap.checkKeyboardModifiers() っていうのがあるよ、と教えてもらい、今回採用した。
 前述した return true も、以前は event:setKeyCode(-1) で入力を抑制してたんだけど、ChatGPT さんに「あんま良くないよ、それ」とかいわれて変えた。

おれの環境では(M2 MacBook Air、sequoia)きびきび動作していい感じ。
 今回驚いたのは、まず ChatGPT さんが Hammerspoon のコード、という超マイナーなものを理解していたこと。そして可読性最悪、とかいいつつも適切なヒントを提案してきたことだ。
 人間がスクリプトとか書く時代は、もう本当に終わりつつあるのかも、みたいなことを実感したよね、これ。