Engineering

Zone → Window 方式のウィンドウマネージャー Framis を作っている

  • macOS
  • Framis
  • Swift
  • window-manager
  • SwiftUI

Framis は macOS 用のウィンドウマネージャーです。既存ツール (Rectangle / Magnet / Moom / Raycast / AeroSpace / yabai) はどれも「Window を選んでから Zone を指定する」方式です。Framis は反転して「Zone を選んでから Window を割り当てる」設計にしました。

従来: focus Safari window → "Move to left half"
Framis: open Arrange Mode → "1" (左 Zone) → 候補から Safari window を選ぶ
FramisZones first. Windows second. | FramisA macOS window manager that flips the script. Pick the spot from a floating panel, then choose what goes there. Free, native, keyboard-driven.

この記事では、既存のウィンドウマネージャーで詰まっていた「どの Safari ウィンドウか」問題、Zone → Window 方式の Arrange Mode の動き、App Store ではなく GitHub Releases で配る前提までを書きます。

既存のウィンドウマネージャーで「どの Safari か」が選べない

Safari を3枚開いた状態を想像してください。1枚目は仕事用、2枚目はデバッグ用に localhost を開いていて、3枚目は雑談用。「2枚目だけを画面右半分に置きたい」というのが、現状のウィンドウマネージャーでは素直にできません。

  • Rectangle / Magnet - 操作対象は常に「現在フォーカスがあるウィンドウ」です。3枚あるうちのどれをフォーカスしているかが操作の前提
  • Raycast Window Management - 標準のショートカットはフォーカス中のウィンドウを操作する形で、デフォルトの preset も「アプリ単位」の粒度に揃っています。getWindowsOnActiveDesktop() のような API でアクティブデスクトップ上のウィンドウ一覧を取れますが、preset として「どのウィンドウを動かすか」をビルトインで持てない
  • AeroSpace / yabai - タイル型でアプリ単位の workspace 移動はできますが、「特定の Window だけ」を狙う標準操作は限定的

実際の作業では、Safari を Cmd+Tab で順に切り替えながら「これだ」と思ったタイミングでショートカットを押す、という操作になりがちです。狙うはずだったウィンドウがフォーカスから外れていて、関係ない Window が動いた、という事故が地味に起きます。

そもそも「どこに置きたいか」のほうが先に決まっているのに、操作の入口が「どの Window か」になっている。これが反転していたら楽に組み直せると思いました。

Zone を先に選んで、その中で Window を選ぶ

Framis の Arrange Mode はホットキーで起動します。起動すると、画面全体が frosted glass の overlay で覆われ、その上に Zone の番号が大きく表示されます。

  1. ホットキーで Arrange Mode を起動
  2. 1」を押すと左半分の Zone が選ばれ、その Zone の上に開いている Window 一覧が popup で出る
  3. j / k で目的の Window へカーソルを移動して Enter
  4. その Window が 1 の Zone に置かれ、モードはそのまま残る
  5. 続けて「2」を押し、別の Window を置き、を繰り返す
  6. Esc で抜ける

「Zone → Window」と頭の中の決定順を一致させるので、考える前に手が動く操作に近づきます。複数 Safari ウィンドウのうち「2枚目だけ」を選びたいケースでも、popup の Window 一覧から目で見て選べるので、Cmd+Tab で当てに行く必要がなくなります。

UI のパターンは、「下に Window 一覧を出す案」「コマンドパレット型で一気にやる案」など6パターンを比較した結果、「Zones + Inline Popup」に落ち着きました。前者は Zone が見づらくなる、後者は操作のキーストロークが増える、といった理由で取りませんでした。

「常駐しない」を product vision に書いた

Framis の product vision には opt-in only を明文化しました。ホットキーで明示的に起動するまでは、overlay も drag フックも edge snap も出ません。常駐 UI を持つ window manager は「うっかり触ってウィンドウが飛んでいく」事故が起きがちで、それを避けるためです。

  • ホットキー起動のみ
  • 起動中は frosted glass の overlay が画面を覆う
  • Esc で抜けたあとは何も残らない
  • 通常作業中に Framis が「乱入してくる」ことはない

これは Raycast / Rectangle と補完関係を意図した設計でもあります。Raycast の Window Management は「朝の初期配置」が得意で、Framis は「日中の組み直し」を担当する、と棲み分けています。

GitHubGitHub - LabeeHive/Framis-releases: Framis — Zone-first window management for macOSFramis — Zone-first window management for macOS. Contribute to LabeeHive/Framis-releases development by creating an account on GitHub.

Accessibility API を使うため App Store には出せない

Framis は Accessibility API (AXUIElement) を使ってウィンドウの位置とサイズを操作します。これは sandbox 環境では呼べないので、App Store では配布できません。Rectangle、AeroSpace、AltTab、Moom 4 といった同種のツールも、いずれも App Store の外で配っています。

そこで Framis も次の形に倒しました。

  • GitHub Releases からダウンロードする
  • Developer ID で署名し、Apple の公証 (notarization) を通す
  • Homebrew (将来) で配る

Framis では、Developer ID Application 証明書をキーチェーンに入れて、xcrun notarytool に App 用パスワードを渡してパッケージを公証しています(notarytool 自体は App Store Connect API キーによる認証もサポートしています)。署名 → DMG → 公証 → GitHub Releases へのアップロードを1コマンドで回せるリリーススクリプトをリポジトリに持っています。

初回起動時には、システム設定 → プライバシーとセキュリティ → アクセシビリティで Framis を許可する必要があります。Accessibility 権限を取らないとウィンドウ操作 API がそもそも反応しないので、ここを通さないと何もできません。許可後の挙動は Rectangle や AeroSpace と同じ感覚です。framis.labee.dev で配布版を案内しているので、Accessibility 権限を通せば Arrange Mode 自体は数キーで触れます。

次に入れたい改善

Window-level preset を入れたいです。 Raycast の Window Management API では「Safari の左半分」のようなアプリ単位の preset しか作れません。「VS Code の main.swift を表示しているウィンドウを左に」「Safari で localhost:3000 を表示しているウィンドウを右に」のような、ウィンドウタイトル単位の preset を Framis では扱えます。Arrange Mode の Window 一覧から手で選ぶのに加えて、保存しておいた preset を1キーで呼び出す動線を足します。

Homebrew (brew install --cask framis) からの配布も検討は進めたいところです。署名と公証の自動化は既にスクリプトで回せるようになっているので、Homebrew tap のメンテだけが残り作業です。

とはいいつつ、PCを起動して最初に立ち上げるBoostrapのような操作ではRaycastが非常に便利なので是非使ってみてください。

www.raycast.comRaycast - Your shortcut to everythingA collection of powerful productivity tools all within an extendable launcher.