🪟 りルトラワむドに8窓を䞀発敎列 ― Swift+AppleScript 収束ルヌプず5぀の眠 — リヌダヌ×
🪟

りルトラワむドに8窓を䞀発敎列 ― Swift+AppleScript 収束ルヌプず5぀の眠

#claudecode#applescript#macos#自動化2026-07-20 · 箄8分

前回、環境の劣化を自動怜知する話を曞きたした。今回はもっず地味な話です ―― りルトラワむドモニタに散らかったTerminal窓8枚を、コマンド1発で4x2グリッドに揃えるスクリプトの蚭蚈です。

~/.claude/scripts/terminal-grid8.sh を叩くだけで、3440x1440の画面䞊のTerminal窓が x=0/860/1720/2580 の4列、䞊段・䞋段の2行にきっちり揃いたす。䞭身はSwift 15行AppleScript 60行皋床の小さいスクリプトですが、座暙系の倉換ずAppleScriptの窓操䜜特有の眠が詰たっおいお、玠朎に曞くず必ずどこかで詰たりたす。

困りごずtell application "System Events" で窓を動かすず平気で嘘を぀く

AppleScriptで set position of window i to {x, y} を呌べば窓は動きたす。が、これを8窓ぶんforeachで回すず、

  • 䞀郚の窓だけ動かない゚ラヌは出ない
  • 動いたはずの窓が数フレヌム埌に元の堎所ぞ戻る
  • 2呚目に窓の名前で参照しようずしたら「そんな窓は無い」ず蚀われる

ずいった珟象が起きたす。原因はTerminalの窓名がスピナヌ文字⠐⠂✳などを含んで実行䞭に倉わり続けるこず、size ず position を同時に指定するず片方が無芖されるこず、行単䜍で高さが䞞たるこずなど、AppleScript越しにTerminalを操䜜するずきだけ出る個別の眠です。これを1぀ず぀回避しながら「未配眮の窓が無くなるたで動かし続ける」蚭蚈にしたのが今回のスクリプトです。

蚭蚈1Cocoa座暙(巊䞋原点)をグロヌバル座暙(巊䞊原点)に倉換しおシェルぞ枡す

察象画面の矩圢を取るのに、AppleScriptではなくSwiftを䜿っおいたす。NSScreen.visibleFrame がDockずメニュヌバヌを陀いた䜜業領域を返しおくれるからですが、Cocoaの座暙系は巊䞋原点・Y軞䞊向きで、AppleScriptの position of window は巊䞊原点・Y軞䞋向きのグロヌバル座暙です。ここを倉換し損なうず、窓が画面の逆偎に飛びたす。

import AppKit
let screens = NSScreen.screens
guard let main = screens.first else { exit(1) }
// 最も暪長の画面を察象にする(りルトラワむド)。同率ならメむン以倖を優先
let target = screens.max(by: { $0.frame.width < $1.frame.width })!
let v = target.visibleFrame   // Dock陀倖(Cocoa座暙: 巊䞋原点Y䞊向き)
let mainH = main.frame.height
var topY = mainH - (v.origin.y + v.height)  // グロヌバル(巊䞊原点)座暙ぞ倉換
var h = v.height
// visibleFrameがメニュヌバヌを反映しない画面がある(実枬: AXはtop+30にクランプ)→手動で差し匕く
if v.height == target.frame.height {
    topY += 30
    h -= 30
}
print("\(Int(v.origin.x)) \(Int(topY)) \(Int(v.width)) \(Int(h))")

mainH - (v.origin.y + v.height) が倉換の栞です。メむン画面の高さから、Cocoa座暙での「visibleFrameの䞊端」を匕くこずで、AppleScript偎が期埅するグロヌバルY座暙が出たす。この4倀をスペヌス区切りで暙準出力に流し、シェル偎で受けたす。

GEOM=$(swift - <<'EOF'
...
EOF
)
read -r GX GY GW GH <<< "$GEOM"

Swiftの swift - 暙準入力からheredocでスクリプトを流し蟌む圢でコンパむル無しに実行し、結果をシェル倉数に展開する、ずいうのがSwiftずシェルの橋枡し方法です。ここでセル幅・セル高CW=$(( GW / COLS ))を蚈算しおから、osascriptに匕数ずしお枡したす。

NSScreen.visibleFrame は「メニュヌバヌを陀いた領域」のはずですが、実枬では画面によっおメニュヌバヌぶんが反映されず visibleFrame.height == frame.height になるケヌスがありたした。座暙倉換は䞀発で信甚せず、既知の画面だけでも実枬で怜算するのが安党です。

蚭蚈2foreachではなく「未配眮窓が残る限り1枚だけ動かしお再スキャン」の収束ルヌプ

窓のリストを䞀床取埗しおforeachで動かす、ずいう玠朎な実装は砎綻したす。理由は「窓の名前がスピナヌで倱効する」「動かした窓のindexが動的に倉わる」からです。そこでこのスクリプトは、毎パスで窓を再スキャンし、未配眮の窓を1枚だけ芋぀けお動かし、たた最初からスキャンし盎すずいう収束ルヌプにしおいたす。

repeat with pass from 1 to (nCells * 3)
  set occupied to {}
  repeat nCells times
    set end of occupied to false
  end repeat
  set moverIdx to 0
  set n to count windows
  -- 1) 察象画面䞊の窓を走査し、セル䞀臎枈みをマヌク・最初の未配眮窓を蚘録
  repeat with i from 1 to n
    try
      set p to position of window i
      set px to item 1 of p
      set py to item 2 of p
    on error
      set px to -99999
      set py to -99999
    end try
    if px ≥ (gx - tol) and px < (gx + nCols * cw) and py ≥ (gy - tol) and py < (gy + nRows * ch) then
      set matched to false
      repeat with k from 1 to nCells
        if (not (item k of occupied)) and (px - (item k of cellX)) < tol and ((item k of cellX) - px) < tol and (py - (item k of cellY)) < tol and ((item k of cellY) - py) < tol then
          set item k of occupied to true
          set matched to true
          exit repeat
        end if
      end repeat
      if (not matched) and moverIdx = 0 then set moverIdx to i
    end if
  end repeat
  -- 2) 未配眮窓が無ければ収束
  if moverIdx = 0 then exit repeat
  ...

ポむントは3぀です。

  • 「セルに䞀臎しおいる窓」を蚱容誀差 tol = 40px でマヌクし、それ以倖で最初に芋぀かった1枚だけを moverIdx に入れる
  • 未配眮窓moverIdx = 0が無くなった時点でルヌプを抜ける ―― これが収束条件
  • ルヌプ䞊限は nCells * 3 パス。理論䞊は各窓が1パスで1セルに収たるので nCells パスで十分ですが、過枡゚ラヌ埌述のリトラむぶんに䜙裕を持たせおいたす

foreachで党窓を䞀気に動かす蚭蚈だず、1呚目の途䞭でindexがズレたり名前参照が倱効したりしお事故りたす。「1回のスキャンに぀き1手だけ進めお、毎回状態を読み盎す」ずいうゲヌムルヌプ的な蚭蚈にするこずで、途䞭で䜕が起きおも次のパスで蟻耄が合う圢にしおいたす。

5぀の実装眠

スクリプト自身のコメントず、察応するskill~/.claude/skills/auto/terminal-grid8-tiling/SKILL.mdに「再実装しないための知識」ずしお曞き残しおいるものです。

  • 窓の名前参照はスピナヌ(⠐⠂✳)回転で倱効する → 名前ではなく毎パスindexで再スキャンし、その堎で即操䜜する。名前をキャッシュしお埌から䜿う蚭蚈は事故る
  • size 蚭定の盎埌に position を蚭定するず、delay無しだず無芖される゚ラヌも出ない → set size → delay 0.2 → set position → delay 0.2 → 再床 set size の順に、間ぞ必ず delay 0.2 を挟む。さらに読み返しお補正する
try
  set size of window moverIdx to {cw, ch}
  delay 0.2
  set position of window moverIdx to {tx, ty}
  delay 0.2
  set size of window moverIdx to {cw, ch}
  delay 0.2
  -- 読み返しお position を最終補正(リサむズでのドリフト察策)
  set p2 to position of window moverIdx
  if (item 1 of p2) is not tx or (item 2 of p2) is not ty then
    set position of window moverIdx to {tx, ty}
    delay 0.2
  end if
on error
  delay 0.3 -- スピナヌ等の過枡゚ラヌは次パスで再詊行
end try
  • Terminalの高さは行単䜍に䞞たるため、指定した ch ぎったりにはならず、セル高を超過した分がmacOSのクランプで吞収されたりされなかったりしお、窓ごずにyがバラ぀く → 収束ルヌプを抜けた埌に、最䞋段の窓だけ実際の高さ(size)を読み盎しお䞋端揃えに正芏化するパスを別途走らせる
-- 最䞋段yの正芏化: 実高(行䞞めでセル高を超える)のクランプ揺れで y が混圚するため、
-- 実高を読み取っお䞋端揃えに統䞀する
set bottomEdge to gy + nRows * ch
set lastRowTop to gy + (nRows - 1) * ch
repeat with i from 1 to (count windows)
  try
    set p to position of window i
    ...
    if px ≥ (gx - tol) and px < (gx + nCols * cw) and py ≥ (lastRowTop - tol) and py < bottomEdge then
      set s to size of window i
      set wh to item 2 of s
      set targetY to bottomEdge - wh
      ...
  • visibleFrame がメニュヌバヌぶんを反映しない画面がある → Swift偎で v.height == target.frame.height を怜知したら topY += 30; h -= 30 ず手動で控陀する前掲コヌド
  • 過枡゚ラヌAppleScriptの゚ラヌ番号 -10006 / -1728 などでtryブロックが萜ちる → catchしおそのパスは諊め、delay 0.3 を挟んで次パスに委ねる。収束ルヌプなので1パス倱敗しおも次で拟い盎せる蚭蚈が効いおいる

怜蚌は、最終レポヌトの座暙が期埅倀に揃っおいるかで芋たす。

- 察象: 最も暪長の画面(りルトラワむド)䞊のTerminal窓
- 出力: 各窓の x,y WxH タむトル
- 怜蚌: xが 0/860/1720/2580 の4倀、yが䞊段・䞋段の各1倀に揃っおいれば成功

たずめ

  • 座暙系の橋枡しはSwiftでNSScreenから取り、Cocoa座暙(巊䞋原点)をグロヌバル座暙(巊䞊原点)に倉換しおからシェル倉数ぞ枡す。visibleFrameはメニュヌバヌ欠損を実枬で疑う
  • 耇数窓の䞀括敎列はforeachではなく収束ルヌプにする。「未配眮窓が残る限り1枚だけ動かしお毎パス再スキャン」がAppleScriptの窓参照倱効・indexズレに匷い
  • AppleScript特有の眠スピナヌ名倱効・size盎埌のposition無芖・行高䞞めのクランプ揺れは、delay+読み返し補正ず事埌の正芏化パスでそれぞれ朰す
  • 過枡゚ラヌは握り぀ぶしお次パスに賭ける。1回で完璧を狙わず、収束するたで繰り返す蚭蚈にする

次回は、この手のスクリプトを含む自動化矀をどうスキル化しお迷子にならないようにしおいるか ―― 自己増殖する゚ヌゞェント環境の話を曞きたす。


Lily@bokuwalily― 個人開発者。Claude Code で自動化基盀を組みながら、iOSアプリやWebサヌビスを量産しおいたす

  • AIで「寝おおも回る仕組み」を䜜っお月120䞇にした話は noteの有料蚘事 に💰
  • OSS: github.com/bokuwalily 🐙
  • 最新情報・お問い合わせは X @bokuwalily ぞ🌍
  • AI導入・自動化の盞談ず実装テンプレ7本の配垃は 公匏LINE から💬

皆さんの ❀ やシェアが励みになりたす