Hammerspoonを使ってカーソルを合わせることなくウィンドウの移動と拡大縮小を行う

今までウィンドウの移動と拡大縮小については、ショートカット化されてきませんでした。

(いちいちウィンドウの頭や右下にカーソルをあわせなくてはいけない例 ✕)
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676735/c0138765-a4e4-2741-dd3c-c9ff19c0bbac.gif" alt="manual.gif]" width="400"/>

本記事では
1. それらにショートカットを設定する方法と
1. それを実現するのに必要な素晴らしいツール(Hammerspoon)

を紹介します。これによりカーソル移動を最小化できます。

(ショートカット化によりカーソルをあわせなくてよくなった例 ◯)
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676735/eee53f04-c6e4-f22a-c3e4-0af59d83a077.gif" alt="move.gif]" width="400"/>

## 前提
- hammerspoon

## hammerspoon について
[hammerspoon](https://github.com/Hammerspoon/hammerspoon)は、OS Xの自動化ツールです。他のウィンドウ管理アプリShiftItの代替ツールとして挙げられています[^shiftitspoon]

インストールはbrewでできます。
```
brew install hammerspoon --cask
```

Qiitaにもいくつか記事があります。

https://qiita.com/mishiwata1015/items/fb7527e5b45f84a90eec

https://qiita.com/mishiwata1015/items/5031f0ca55621301b800

https://rochefort.hatenablog.com/entry/2017/02/25/070000

## hammerspoon を触ってみる
hammerspoonは、とても **手軽**に **プログラマブル**に OS Xの設定が記述できます。
その一例を紹介します。

インストール後、menu barのハンマーのアイコンから「Open Config」を押して以下をコピペしてみてください。
```lua
DURATION = 0.05
DELTA = 10
function moveUp()
    local win = hs.window.focusedWindow()
    local f = win:frame()
    f.y = f.y + DELTA
    win:setFrame(f, DURATION)
end
function moveDown()
    local win = hs.window.focusedWindow()
    local f = win:frame()
    f.y = f.y - DELTA
    win:setFrame(f, DURATION)
end
function moveLeft(bound)
    local win = hs.window.focusedWindow()
    local f = win:frame()
    f.x = f.x - DELTA
    win:setFrame(f, DURATION)
end
function moveRight()
    local win = hs.window.focusedWindow()
    local f = win:frame()
    f.x = f.x + DELTA
    win:setFrame(f, DURATION)
end
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "J", moveUp,nil,moveUp)
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "K", moveDown,nil,moveDown)
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "H", moveLeft,nil,moveLeft)
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "L", moveRight,nil,moveRight)
```

ハンマーアイコンから「Reload Config」を押すと設定がロードされ、`cmd+alt+ctrl+j,k,h,l`でフォーカスしているウィンドウをショートカットから移動することがわかると思います。

## 設定方法
それでは冒頭の操作をショートカット化する方法を紹介します。
前述と同様に「Open Config」から以下の設定をコピペし「Reload Config」します。

```init.lua
DURATION = 0.05
INTERVAL_SEC = 0.05
SCALE = 2.0

repeatMove = nil
function startMoveWindow()
    prevPoint = hs.mouse.absolutePosition()
    prevFrame = hs.window.focusedWindow():frame()

    repeatMove = hs.timer.doEvery(INTERVAL_SEC, function()
        local nextPoint = hs.mouse.absolutePosition()
        local win = hs.window.focusedWindow()
        local f = win:frame()
        f.x = prevFrame.x - (prevPoint.x - nextPoint.x) * SCALE
        f.y = prevFrame.y - (prevPoint.y - nextPoint.y) * SCALE
        win:setFrameInScreenBounds(f, DURATION)
        prevPoint = nextPoint
        prevFrame = hs.window.focusedWindow():frame()
    end)
    repeatMove:start()
end
function stopMoveWindow() repeatMove:stop() end
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "A", startMoveWindow, stopMoveWindow, nil)

repeatResize = nil
function startResizeWindow()
    prevPoint = hs.mouse.absolutePosition()
    prevFrame = hs.window.focusedWindow():frame()

    repeatResize = hs.timer.doEvery(INTERVAL_SEC, function()
        local nextPoint = hs.mouse.absolutePosition()
        local win = hs.window.focusedWindow()
        local f = win:frame()
        f.w = prevFrame.w - (prevPoint.x - nextPoint.x) * SCALE
        f.h = prevFrame.h - (prevPoint.y - nextPoint.y) * SCALE
        -- bound sizeA
        local sf = hs.screen.find(f):frame()
        if (sf.x + sf.w < f.x + f.w) then f.w = sf.x + sf.w - f.x end
        if (sf.y + sf.h < f.y + f.h) then f.h = sf.y + sf.h - f.y end
        win:setFrame(f, DURATION)
        prevPoint = nextPoint
        prevFrame = hs.window.focusedWindow():frame()
    end)
    repeatResize:start()
end
function stopResizeWindow() repeatResize:stop() end
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "S", startResizeWindow, stopResizeWindow, nil)
```

## 操作例 
- `cmd+alt+ctrl+a`+マウス操作でウィンドウ移動
- 画面を超えないように止まってくれます (`setFrame`に変えると無効にできます)
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676735/eee53f04-c6e4-f22a-c3e4-0af59d83a077.gif" alt="move.gif]" width="400"/>

- `cmd+alt+ctrl+s`+マウス操作で拡大/縮小
- 同様に画面を超えないように拡大が止まります
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676735/5c7be8c4-3660-2d37-5b9f-73886a8d4ff9.gif" alt="resize.gif]" width="400"/>

ショートカットやSCALEはお好みで変えてください。

## 終わりに
少なくともウィンドウの頭にカーソルを合わせる時間は削れたかと思います。

hammerspoonは便利なツールなので、興味のある方は、公式のチュートリアルやAPIドキュメントを読むといろいろ膨らむと思います。
- https://www.hammerspoon.org/go
    - 設定をリロードするショートカットも紹介されています
- https://www.hammerspoon.org/docs/index.html

[^shiftitspoon]: A highly recommended alternative to ShiftIt that can be implemented using Hammerspoon. https://github.com/fikovnik/ShiftIt

[^yabai]: 僕はyabaiユーザーですが、オーバーレイ可能(float)なwindowを扱いときが多々あり、結構辛いです