世界を動かす技術を、日本語で。

ハマースプーン

概要

  • Hammerspoon は、 OS X の高度な自動化ツール
  • Luaスクリプト でシステム機能を制御可能
  • 拡張機能 により多様な操作が可能
  • インストール方法 は手動またはHomebrew
  • 設定ファイル 作成が利用開始の第一歩

Hammerspoonとは何か

  • Hammerspoon は、 OS X の強力な自動化ツール
  • コア部分は オペレーティングシステムLuaスクリプトエンジン の橋渡し役
  • 拡張機能 (Extensions)がシステムの特定機能をユーザーに公開
  • Luaスクリプト を使い、多様なOS X環境の制御が可能

インストール方法

  • 手動インストール
    • 最新リリースをダウンロード
    • Hammerspoon.app をダウンロードフォルダから Applications へドラッグ
  • Homebrew利用
    • コマンド: brew install hammerspoon --cask

初期設定とリソース

  • デフォルト状態では 何も動作しない 仕様
  • 利用開始には ~/.hammerspoon/init.lua を作成し、スクリプトを記述
  • 参考リソース
    • Getting Started Guide (導入ガイド)
    • APIドキュメント
    • FAQ
    • ユーザーによるサンプル設定
    • Contribution Guide (開発者向けガイド)
    • IRCチャンネル (#hammerspoon on Libera)
    • Google Group (サポート用)

プロジェクトの歴史

  • HammerspoonMjolnir からのフォーク
  • Mjolnir は最小限のアプリを目指し、拡張機能を外部で管理
  • Hammerspoon はより統合された体験を提供

プロジェクトの今後

  • 拡張機能 によるシステムAPIのカバー範囲拡大
  • 拡張機能同士の 連携強化
  • ユーザー体験 の向上

Hackerたちの意見

これが俺の設定全部だよ。 hs.hotkey.bind({"ctrl"}, "D", function() hs.grid.show() end) 他のいろんなウィンドウマネージャーも試したけど、俺にとっては使いやすさが一番だね。 (1) Ctrl-Dでグリッドを表示、 (2) ウィンドウの左上のコーナーにしたい場所の文字を入力、 (3) 右下のコーナーにしたい場所の文字を入力でウィンドウがリサイズされる。

EOFを挿入することはあんまりないけど、ターミナルでCTRL+Dと競合しないの?

これすごい! 俺はもう少し複雑な設定をしてて、Appleが最近追加したような感じで、片側からリサイズできるようにしてるんだけど、もっと柔軟性があるんだ。 でもこれはめっちゃ面白いね、シェアしてくれてありがとう!

いい感じだけど、設定を同期させたり、複数のマシンを調和させる必要が出てくると、HammerspoonとLuaでドットファイルの頭痛が増える。 ウィンドウルールやアプリ固有の動作、モニターの変更を扱うような複雑なロジックを追加すると、そのホットキーのシンプルさが失われて、延々と調整が続くことになる。 それでも、マウスを使わずに済む数少ない柔軟な選択肢の一つだから、macOSでは古臭さを感じない。 どこにでもトレードオフがあるけど、他には本当にこのコントロール感に匹敵するものはないね。

わぁ…それは…すごいね。Hammerspoonをずっと使ってるけど、そんな機能があるなんて知らなかった。いじってたら、hs.grid.setGrid('4x4')でグリッドサイズを拡張できることに気づいたし、さらにhs.grid.ui.textSize = 30でテキストサイズを縮小することもできる。あと、代替キーボードレイアウト(例えばColemak)を使ってるなら、hs.grid.HINTSでそれを使うように設定できるよ。この機能、ほんとにすべてを考慮してるね。

Hammerspoonは俺のMacを支えてる重要な存在。 このアプリでやってることのスタートリストを少し紹介するね:

  • 開いてるSafariのタブをObsidianドキュメントにダンプ
  • 新しいウィンドウを開くための「hyper」(Ctrl-Opt-Cmd)キーを追加:
    • Safari
    • Finder
    • Terminal / Ghostty
    • VS Code
    • Notes
  • Hammerspoon/AeroSpace/Sketchybarの設定を編集
  • Hammerspoonの設定をリロード
  • Sketchybarをリロード
  • Finder以外のDockアプリを全部終了
  • スクリーンロック
  • システムスリープ
  • VS Codeで前面のFinderフォルダを開く
  • Archive.todayで前面のSafari URLを開く
  • 前面のSafariウィンドウのタブ数を表示
  • 前面のアプリのバンドルIDを表示
  • 現在の音楽トラックについて通知を投稿
  • Logi Litraライトをコントロール(色温度や明るさを調整)
  • クライアント作業タイマーをスタート/ストップ
  • AeroSpaceに連携して:
    • ウィンドウを別のモニターに移動
    • 2つのウィンドウレイアウトを実行
    • その2つのウィンドウを入れ替え
    • 他のワークスペースのウィンドウを全部閉じる
    • すべてのウィンドウを最初のワークスペースに集める
    • クラッシュした場合にバックグラウンドアプリを維持
    • ゴミ箱に入れたディスクイメージをアンマウントするように促す
  • Skimにバインドして、特定のセクションにジャンプするためのMarkdown URLを使う。

キーボード関連はKarabiner Elementsでやるのがいいよ。

  • 開いてるSafariのタブをObsidianドキュメントにダンプするの、私もやってみたい!どうやってるのか教えてくれない?それとも簡単すぎて説明する価値ない感じ?(HSについてはまだ深く見てないんだ。)

最近はほとんどこれを2つ(関連する)ことにしか使ってないんだ。 - 開いてるTeamsウィンドウのリストをチェックして、もし標準じゃないのがあったら、会議中だと思ってHomeAssistantにウェブフックして会議用ライトの「アクティブ」[2]プリセットを選ぶ。 - 自分の仕事のiCal[1]をダウンロードして、もし会議が近づいてたら(<~15分)、ウェブフック-HASSで会議用ライトの「保留」プリセットを使う。 [0] ただのWS2812Bの短いストリップをESP32に接続してWLEDを動かしてるだけ。 [1] 元々はoutlook.comの共有リンクにHTTPでアクセスしてたけど、認証が必要になっちゃった(共有リンクに認証が必要なんて、ほんとにクソだよね)。Azure SDKを見てみたけど、あれはもう訳がわからなかった。結局、仕事のiCalをAppleカレンダーにインポートして、そのリンクをHammerspoonで使うことになった。ああ、笑ったよ。特に「自分のカレンダー」には実際の会議の約40%しか入ってないことに気づいたときはね。なぜか「自分のカレンダー」は4つか5つの痛みの混合物で、「自分のカレンダー」のiCalはそのうちの一つだけ。しかも、役に立たないやつ。 [2] いろいろあって、「カメラ」は「カメラをオンにしなきゃいけない会議」、「アクティブ」は「多分話さなきゃいけない」、「パッシブ」は「話さないつもり」、「サイレント」は会社のプレゼンみたいに、Teamsで退屈なパワーポイントを見るだけの時に使ってる。

Hammerspoonでタイル型ウィンドウマネージャーを偽装して、特定のコーナーやサイズに合わせてリサイズしてるよ: -- 比率に基づいてリサイズする関数

function ratioResize(xr, yr, wr, hr)
  return function ()
    local win = hs.window.focusedWindow()
    win:moveToUnit({x=xr,y=yr,w=wr,h=hr})
  end
end

-- 4つのコーナー、異なるサイズ hs.hotkey.bind({"cmd", "ctrl"}, "w", ratioResize(0, 0, 2/5, 2/3)) hs.hotkey.bind({"cmd", "ctrl"}, "e", ratioResize(2/5, 0, 3/5, 2/3)) hs.hotkey.bind({"cmd", "ctrl"}, "s", ratioResize(0, 2/3, 2/5, 1/3)) hs.hotkey.bind({"cmd", "ctrl"}, "d", ratioResize(2/5, 2/3, 3/5, 1/3)) ウィンドウを他のモニターに移動するために: -- 次のスクリーンに送る hs.hotkey.bind({"cmd", "ctrl"}, ";", function() local win = hs.window.focusedWindow() local screen = win:screen() local next_screen = screen:next() win:moveToScreen(next_screen) end)

Aerospaceはめっちゃおすすめだよ!いくつかのアプローチを試したけど、セキュリティを妥協したくなかったから、i3から来た人にはすごく合うと思う。 https://github.com/nikitabobko/AeroSpace

俺も似たような使い方してるけど、サイドバイサイドや左、中、右のスポットも追加してる。これにはHammerspoonだけ使ってるけど、これだけでも全然価値があるよ。ウィンドウのサイズや位置を数学で指定するんだ。狂ってるよね。

local mode = hs.screen.primaryScreen():currentMode()
local mods = {"ctrl", "alt", "cmd"} -- そのキーを押しまくる --
local w = 1094 -- GitHubやHNではクリップなし
local h = 1122 -- 高め
local x_1 = 0 -- 左端
local x_2 = math.max(0, (mode.w - w - w) / 2) -- 左中
local x_3 = (mode.w - w) / 2 -- 中央
local x_4 = math.min(mode.w - w, x_2 + w + 1) -- 右中
local x_5 = mode.w - w -- 右端
local y = 23 -- メニューバーの下の画面上部

hs.hotkey.bind(mods, "2", function() move_win(0, y, mode.w, mode.h) end) -- 最大化
hs.hotkey.bind(mods, "3", function() move_win(x_1, y, w, h) end)
hs.hotkey.bind(mods, "4", function() move_win(x_2, y, w, h) end)
hs.hotkey.bind(mods, "5", function() move_win(x_3, y, w, h) end)
hs.hotkey.bind(mods, "6", function() move_win(x_4, y, w, h) end)
hs.hotkey.bind(mods, "7", function() move_win(x_5, y, w, h) end)

end

function move_win(x, y, w, h)
    hs.window.focusedWindow():setFrame(hs.geometry.rect(x, y, w, h))
end

Moomは本当に最高だね。

miroウィンドウマネージャープラグイン、めっちゃ好き!: https://github.com/miromannino/miro-windows-manager いろんなケースに合わせて半分や三分の一の設定を試せるのがいいよね。

https://github.com/peterklijn/hammerspoon-shiftit ShiftIt(素晴らしいプロジェクトだけど、もう終わってる)をHammerspoonで再実装して使ってるよ。すごく充実してる。

Zoomの画面共有コントロールを隠すために使ってる。 Escを押しても戻ってこないようにするために: -- Zoomの「共有」ウィンドウを隠す

local zoomWindow = nil
local originalFrame = nil
hs.hotkey.bind({"cmd", "ctrl", "alt"}, "H", function()
  print("> Zoomを隠そうとしてる")
  if not zoomWindow then
    print("> ウィンドウを探してる")
    zoomWindow = hs.window.find("zoom share statusbar window")
  end
  if zoomWindow then
    print("> ウィンドウを見つけた")
    if originalFrame then
      print("> 復元中")
      zoomWindow:setFrame(originalFrame)
      originalFrame = nil
      zoomWindow = nil
    else
      print("> 隠してる")
      originalFrame = zoomWindow:frame()
      local screen = zoomWindow:screen()
      local frame = zoomWindow:frame()
      frame.x = screen:frame().w + 99000
      frame.y = screen:frame().h + 99000
      zoomWindow:setFrame(frame)
    end
  else
    print("> ウィンドウが見つからない")
  end
end)

まじで、これだけのためにインストールする価値ありすぎ!

特定のUSBハブにMacBookを接続/切断するときにWi-Fiをオン/オフするために使ってる:

local usbWatcher = hs.usb.watcher.new(function(device)
  if device.productName == "EMEET SmartCam C960" then
    if device.eventType == "added" then
      hs.execute("networksetup -setairportpower en0 off")
      hs.notify.new({title="Wi-Fi", informativeText="無効化(USBデバイス接続中)"}):send()
    elseif device.eventType == "removed" then
      hs.execute("networksetup -setairportpower en0 on")
      hs.notify.new({title="Wi-Fi", informativeText="再有効化(USBデバイス取り外し)"}):send()
    end
  end
end)
usbWatcher:start()

Ethernetアダプタが優先度高ければ、そんなことは必要ないはずだよ。とはいえ、他の理由があるかもね。 https://support.apple.com/en-ca/guide/mac-help/mchlp2711/mac

macOS用のウィンドウ管理ツールを探してたんだけど、RectangleやMagnetを試して、他にもいろいろ試したよ。そしたらHammerspoonがあることに気づいて、ウィンドウを自分好みに再現できるんだ。もう1年使ってるけど、99%完璧だよ。特定のアプリ(くっ、Firefox)でたまに変な状態になることもあるけど、それは我慢できる。アクティブなWi-Fiとモニターの設定を組み合わせて、家にいるのか、仕事中なのか、家で仕事してるのかを検出して、特定のレイアウトに一発でウィンドウを並べられるのもいいね。

Moomはめっちゃ使いやすいよ。

ちょっと宣伝させてね - https://github.com/agzam/spacehammer "Spacemacs|DoomにインスパイアされたHammerspoonモーダルツールキット" これなしではMacで作業できないよ。例えば「alt+spc a b」(アプリ -> ブラウザ)や「alt+spc m j/k」(メディア -> 音量アップ/ダウン)とか、どんなアプリのテキストもエディタ(今はEmacs)で編集できるんだ。スペルチェックやシソーラス、翻訳、LLMなど、いろんなツールが使えるよ。お気に入りのWM(今はYabai使ってる)に接続して、他にも面白いことがたくさんできる。Fennelで書かれてるから、接続されたREPLでフィードバックループを使って開発できるんだ。例えば、ClaudeにSlackアプリやFirefoxの中を見てもらったり、面白い自動化を作ったりできる。エディタから離れずにね。

「alt+cmd m j/k」(メディア -> 音量アップ/ダウン)キーボードに音量調整のボタンが付いてたらなぁ…あ、待って。タッチバーのMacを使ってるなら、そりゃ理解できるけどね。

Hammerspoonで作ったもの、使ってみて! - macOSのどこでもVimモード: https://github.com/dbalatero/VimMode.spoon - 修飾キー+クリック/ドラッグでウィンドウをリサイズまたは移動: https://github.com/dbalatero/SkyRocket.spoon - 修飾キーを押し続けると、すべてのキーバインドのオーバーレイヘルパーを表示: https://github.com/dbalatero/HyperKey.spoon あと、私のランダムなスクリプトや設定の山: https://github.com/dbalatero/nixpkgs/tree/main/home/modules/...

Hammerspoonのメンテナーだよ - みんなのコメントを読んで楽しんでるし、今は主にv2に取り組んでるから、みんながイライラしないことを願ってる。LuaからJavaScriptに切り替えるんだ:D

v2 ATMがLuaからJavaScriptに切り替わるみたいだね :D たぶん、[カレンダーをチェック] 18日後にリリースされるのかな?

この切り替えは開発者の使いやすさのためなのか、それとも言語の人気のためなのか、ちょっと気になるな。

HammerspoonはLuaを書く唯一の理由なんだ。実はこの言語がすごく好きなんだよね。JavaScriptの方が実用的な選択だとは思うけど、それでもちょっと悲しいな。

これで数年前にJavaScriptを追加したAppleScriptとの相乗効果が生まれるかな?

Appleが何年も放置してきたことをやってくれて本当にありがとう!AppleScript/JXAを放置してたのは犯罪だよ。