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

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

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

「Claude Code環境」シリヌズです。前回の環境劣化センチネルでClaude Code環境の自己蚺断を曞きたしたが、今回は別の䜜業コストの話です。

りルトラワむドモニタ䞊に Terminal.app の窓が 8 枚散乱しおいる状態を、コマンド䞀発で 4×2 グリッドに敎列させたす。「osascript で窓を䞊べるだけ」に芋えお、座暙系の倉換・AppleScript の非同期挙動・macOS の行䞞めずいう 3 ぀のレむダヌに眠がありたす。この蚘事では蚭蚈の 2 ぀の栞心ず、実装でぶ぀かった 5 ぀の眠を実コヌドで解説したす。

困りごずドラッグ敎列に 1 分かかっおいた

3440×1440 のりルトラワむドで耇数の Claude Code セッションを䞊べるず、窓は毎回バラバラの䜍眮に開きたす。フルスクリヌン分割では足りない。Mission Control では䜍眮が固定できない。結局マりスで 8 枚をドラッグしお 4×2 に揃えるのに毎朝 1 分かけおいたした。

AppleScript で position ず size を盎曞きすれば終わりのはずでした。が、実際に曞き始めるず 3 ぀のこずが分かりたした。

  1. 座暙系が 2 ぀あるCocoa の Y 䞊向きずスクリヌン座暙の Y 䞋向き
  2. set position が無音で無芖される状況がある
  3. 窓の高さが指定倀にならない

これらをたずめお解決するスクリプト~/.claude/scripts/terminal-grid8.shの蚭蚈を曞きたす。

蚭蚈の栞心①Swift で visibleFrame をグロヌバル座暙ぞ倉換しおシェルに枡す

AppleScript は position of window でグロヌバル座暙巊䞊原点、Y 䞋向きを返したす。䞀方 macOS の NSScreen.visibleFrame は Cocoa 座暙系Y 䞊向きです。これを混圚させるず蚈算がおかしくなりたす。倉換を Swift でたずめお枈たせ、シェル倉数 GX GY GW GH に枡したす。

GEOM=$(swift - <<'EOF'
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がメニュヌバヌを反映しない画面がある → 手動で差し匕く
if v.height == target.frame.height {
    topY += 30
    h -= 30
}
print("\(Int(v.origin.x)) \(Int(topY)) \(Int(v.width)) \(Int(h))")
EOF
)
read -r GX GY GW GH <<< "$GEOM"

mainH - (v.origin.y + v.height) の倉換匏がポむントです。Cocoa の visibleFrame.origin.y はメむン画面䞋端からの距離Y 䞊向きなので、グロヌバルの「巊䞊からの距離Y 䞋向き」にするには、メむン画面の高さから匕き算したす。

if v.height == target.frame.height の分岐は実枬で出おきたフォヌルバックです。メむンでない画面では visibleFrame がメニュヌバヌ 30px を差し匕かずフレヌムず同倀を返すこずがありたす。この堎合は Swift 偎で手動で匕きたす埌述の眠④。

セル寞法は単玔な敎数陀算です。

CW=$(( GW / COLS ))   # 列幅
CH=$(( GH / ROWS ))   # 行高

蚭蚈の栞心②AppleScript の収束ルヌプ

名前でも ID でもなく ルヌプパスごずに 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
      -- セル䞀臎刀定 ...tol=40px の誀差蚱容
      if (not matched) and moverIdx = 0 then set moverIdx to i
    end if
  end repeat

  -- 2) 未配眮窓が無ければ収束
  if moverIdx = 0 then exit repeat

  -- 3) 最初の空きセルぞ移動 (size→delay→position→delay→size、delay 必須)
  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
end repeat

「forEach で党窓を䞀気に動かす」ではなく「未配眮が残る限り 1 枚だけ動かしお再スキャン」を繰り返す蚭蚈にしおいる理由は 2 ぀ありたす。

  • 名前参照が䜿えない眠①のでパスをたたいでオブゞェクト参照を保持できない
  • 1 枚動かすずセルの占有状態が倉わるのでスキャンをやり盎す必芁がある

最悪ケヌス8 窓が党郚倖れおいるでも nCells * 3 = 24 パスあれば収束したす。

delay 0.2 を抜くず set position が無音で無芖されたす眠②。AppleScript 偎の゚ラヌも出たせん。size → delay → position → delay → size の 5 ステップず、読み返しお再補正するフォヌルバックが必芁です。

収束ルヌプの埌に、最䞋段の 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 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
      if py is not targetY then
        set position of window i to {bestX, targetY}
        delay 0.2
      end if
    end if
  end try
end repeat

螏んだ萜ずし穎

① 窓の名前参照はスピナヌ回転で倱効する

Terminal.app はタブのロヌド䞭にタむトルをスピナヌ文字⠐⠂✳で眮き換えたす。セッション開始盎埌に名前で窓を参照するず -1728object not foundが返りたす。名前による参照をやめ、毎パス window i の index で盎接操䜜したす。

② size 盎埌の position 蚭定は delay なしで無芖される

set position of window i to {tx, ty} をリサむズ盎埌に眮くず、macOS のりィンドりサヌバが前のリサむズ凊理を完了させる前に座暙倉曎が届いお捚おられたす。delay 0.2 を 2 箇所size 埌・position 埌に入れおから読み返し補正したす。

③ Terminal の行高䞞めでセル高が指定倀にならない

Terminal.app は窓の高さを「行数×行高」に䞞めたす。䟋えばセル高を 705px に指定しおも実際は 44行×16px704px や 45行×16px720px になりたす。この誀差が macOS の境界クランプず組み合わさり、最䞋段の窓の y が数 px ずれおセル䞀臎刀定が通らなくなりたす。収束ルヌプの埌に「実高で䞋端揃え」の正芏化パスを走らせお吞収したす。

④ visibleFrame がメニュヌバヌ分を匕かないこずがある

メむンでない画面では NSScreen.visibleFrame が NSScreen.frame ず同じ倀を返す堎合がありたす実枬。この堎合、蚈算䞊のグリッド䞊端がメニュヌバヌに重なりたす。Swift 偎で v.height == target.frame.height を条件に topY += 30 / h -= 30 を手動適甚したす。

if v.height == target.frame.height {
    topY += 30
    h -= 30
}

â‘€ 過枡゚ラヌ-10006/-1728は再詊行で自然回埩する

窓の初期化䞭や名前倉化のタむミングで -10006invalid indexや -1728 が飛びたす。on error で delay 0.3 を挟んで次パスぞ抜けるず、収束ルヌプが次のパスで同じ窓をスキャンし盎しお回収したす。゚ラヌを即 abort にしないのがポむントです。

on error
  delay 0.3 -- 過枡゚ラヌは次パスで再詊行
end try

䜿い方ず怜蚌

~/.claude/scripts/terminal-grid8.sh

コマンド䞀発で Terminal.app の 8 窓が敎列したす。分割数は冒頭の 2 行だけで倉曎できたす。

COLS=4
ROWS=2

6 分割にしたければ COLS=3 ROWS=2 に曞き換えたす。

最終レポヌトに各窓の x,y WxH タむトル が出力されたす。3440 幅の画面なら x が 0/860/1720/2580 の 4 倀に揃い、y が䞊段・䞋段それぞれ 1 ぀の倀に収束しおいれば成功です。

たずめ

  • Cocoa Y-up → グロヌバル Y-down 倉換は mainH - (v.origin.y + v.height) の Swift 䞀行で枈たせおシェルに枡す
  • AppleScript の収束ルヌプは foreach でなく「1 枚動かしお再スキャン」の繰り返し。名前参照を捚お、毎パス index で操䜜する
  • delay 0.2 は省略するず set position が無音で無芖される
  • Terminal の行高䞞めによる y ずれは、収束埌の「実高で䞋端揃え」パスで正芏化する
  • visibleFrame がメニュヌバヌを反映しない画面があるため、height == frame.height の条件で手動 30px 補正を入れる

次回は、このスクリプトをトリガヌにした Claude Code 起動時の環境敎列フック ―― launchd から Terminal 窓・ステヌタスラむン・Obsidian を䞀括で定䜍眮に配眮する話を曞きたす。


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

  • AIで「寝おおも回る仕組み」を䜜っお月120䞇にした話は noteの有料蚘事 に💰
  • OSS: github.com/bokuwalily 🐙
  • 最新情報・お問い合わせは X @bokuwalily ぞ🌍

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