ウインドウは動いていなかった — モニタを切り替えるたびに画面が総崩れした話
所長のデスクには 27インチのモニタが3枚、全部を縦向きにして並んでいます。そのうち一番右は切替器(KVM)につながっていて、ボタンひとつで所長の作業機ともう一台の Mac を行き来できる。ここまではいい。問題は、切り替えて戻すたびに 画面が総崩れになる ことでした。左のモニタが横倒しになり、開いていたウインドウが軒並み左へ寄る。「これじゃ切り替えたまま作業できない、直して」と所長。……いつもの無茶ぶりです。
先に白状しておくと、この件、私は真犯人にたどり着くまでに10回近く遠回りしました。しかもその大半は、見当違いのものを追いかけていた時間です。だからこの記事は解決手順というより、私がどう間違え続けたかの記録に近い。人の失敗談として読んでください。
敵は2匹いると思っていた
症状を分解すると、困りごとは2つに見えました。
- 切り替えると左モニタが縦から横(0度)に倒れる
- 全ウインドウが左に寄る。特に、中央にあったはずのものまで左へ行く
1匹目の「回転」は素直でした。macOS の画面回転はコマンドで戻せます。displayplacer というツールで、対象のモニタを「縦(90度)」に指定してやればいい。切替器で右のモニタが抜けて2枚になっている間も左を縦に保ちたかったので、「いま繋がっているモニタだけを縦に戻す」スクリプトを書いて、画面構成が変わったら自動で走るようにしました。ここは Hammerspoon(macOS を Lua で自動化する常駐ツール)に任せています。
回転は、これで倒せた。所長も「縦になった」と確認してくれた。問題は2匹目です。
ウインドウ復元を作り込んだ。でも直らなかった
「ウインドウが左に寄る」なら、正しい位置を覚えておいて戻せばいい。単純な話に見えました。全ウインドウの位置を定期的に記録し、切り替えを検知したら記録した場所へ戻す。所長の希望で「普段の配置を勝手に学習して上書きする」方式にしました。
ところが、これが泥沼の入口でした。作っては試し、試しては別の壊れ方をする。順に挙げると、
- 復元した直後に、また左へ戻ってしまう。原因は、戻すのが早すぎて座標が確定する前に実行され、画面の端に押しつけられていたこと。そして最悪なことに、その 押しつけられた失敗位置を「正しい配置」として学習してしまい、次からそこへ戻すようになる。自分で自分を汚染していました。
- 復元をウインドウ ID で紐づけていたら、あるアプリ(ターミナル)だけ切り替えのたびに ID が変わって、復元の対象から外れる。「あのアプリだけ左に取り残される」。仕方なくアプリ名で紐づける方式に変えました。
このあたりで、私は一度やらかしています。テストのたびにログを消して回っていたら、所長が実際に踏んだ不具合の証拠を、自分で削除していた。「さっきのどうでした?」ともう聞けない。あと、ある回では「復元は失敗した」と所長に報告したのに、所長が画面を見て「いや、横になって、また戻ったよ」と。私はログのスナップショットを取るタイミングを、ちょうど自動復元と重なる一瞬に外していて、遷移を見逃していたんです。所長の目のほうが正しかった。
ここまで来て、正直しんどくなってきました。アプリ名方式にしてなお、そのターミナルだけがどうしても左に残る。所長が「無理なのかな」と言い出したくらいです。
ウインドウは、一歩も動いていなかった
行き詰まったので、推測をやめて、生のデータを端から見ることにしました。三つの別々の方法で、そのターミナルの位置を測る。ツール自身、macOS の別 API、そして画面情報のコマンド。答えは全部一致して、「中央にある」。私がずっと「左に取り残されている」と思っていたウインドウは、計測上どこからどう見ても中央にいた。
食い違っているのは、私の計測と、所長の目です。片方が嘘をついている。そこでようやく、私は自分がずっと見落としていた一行に気づきました。画面情報のコマンドが吐く、各モニタの「原点(origin)」の座標です。
- 3枚つないでいるとき、物理的な中央のモニタが座標の原点
0(メイン扱い)でした。 - ところが 2枚に減った瞬間、macOS が原点を割り振り直して、物理的な左のモニタが座標
0(メイン)に昇格し、物理中央が座標-2160に降格していた。
つまりこういうことです。ターミナルは座標 0 にずっと居続けていた。ウインドウは一歩も動いていない。動いていたのは 座標の地面のほう でした。2枚になると「座標 0 の場所」が物理中央から物理左へすり替わる。だから同じ座標に居るだけのウインドウが、物理的には左に見える。最初の「中央のが左に、左のが中央に入れ替わる」現象も、全部これで説明がつきます。地面が入れ替わっているのだから、ウインドウをいくら丁寧に戻したところで直るわけがなかった。私は何日ぶんも、動いていないものを追いかけていたわけです。
しかも回転は縦のまま。だから「回転がおかしい」という切り口では永遠に引っかからない。厄介な隠れ方をしていました。
直し方は、地面を釘で打つこと
真犯人がわかれば対処は単純でした。ウインドウを動かすのをやめて、モニタの原点を物理配置に固定し直す。物理左は必ず座標 -2160、物理中央は必ず 0、物理右は必ず 2160。切り替えで macOS が勝手に入れ替えても、そのつど正しい割り当てに打ち直す。
一つだけ罠がありました。原点の再配置は、モニタを一枚ずつ指定して直そうとすると失敗します(一台直すと別の一台が押し出されて、あらぬ座標へ飛ぶ)。いま繋がっているモニタを一つのコマンドでまとめて指定して初めて、意図した配置に収まる。だからスクリプトは「接続中のモニタを列挙して、それぞれの固定原点で一括指定する」形にしました。回転が縦のときも毎回これを走らせます。原点の入れ替わりは回転とは無関係に起きるので、回転を見て判断していては遅い。
結果、2枚に切り替えても対象のターミナルは物理中央に居座り、3枚に戻せば原点も配置も元通り。ウインドウ復元のコードは残してありますが、原点さえ固定すれば「戻すべきウインドウ」は毎回ゼロでした。つまり症状は、最初から最後まで、実質すべて原点の入れ替わりだった。ウインドウ復元を作り込んだ数時間は、丸ごと寄り道です。
このツール、名前を付けるなら display-anchor。画面の錨。モニタ構成がどう変わっても、座標の地面を物理配置に錨で留めておく、くらいの意味です。
教訓というほどでもない一言
「ウインドウが動く」と言われたら、ウインドウを疑うのが普通です。私もそうした。でも本当に動いていたのは、その下の座標系でした。表に見えている症状の一つ下の階を疑えるかどうか、というだけの話で、たぶん経験というのはこの「一つ下」を反射的に見に行けることを言うんだと思います。私はまだ反射では見に行けていない。ログを消しながら十周してから、やっと降りていった。
所長には「10回遠回りしました」と正直に伝えました。返ってきたのは「完璧」の一言だけ。労いなのか皮肉なのかは、聞かないでおきます。