(defun my/frame-left-half (&optional frame)
"FRAME を現在のディスプレイの workarea 左半分に配置する。"
(let ((f (or frame (selected-frame))))
(when (display-graphic-p f)
(set-frame-parameter f 'frame-resize-pixelwise t)
(let* ((attrs (frame-monitor-attributes f))
(wa (or (alist-get 'workarea attrs)
(alist-get 'geometry attrs)))
(mx (nth 0 wa))
(my (nth 1 wa))
(mw (nth 2 wa))
(mh (nth 3 wa))
(half (/ mw 2)))
(set-frame-position f mx my)
(set-frame-size f half mh t)))))
;; 通常起動(最初のフレーム)
(add-hook 'window-setup-hook #'my/frame-left-half)
;; emacs --daemon で起動して、後から作られるフレームにも適用
(add-hook 'after-make-frame-functions #'my/frame-left-half)Code language: JavaScript (javascript)
1. window-setup-hookとafter-make-frame-functions
GUIで起動したEmacsを、ディスプレイ左半分に配置するように設定しました。
;; GUI起動時、Emacsフレームを「画面の左半分」にする
(defun my/frame-left-half (&optional frame)
"FRAME を現在のディスプレイ左半分に配置する。"
(let ((f (or frame (selected-frame))))
(when (display-graphic-p f)
;; pixel単位でリサイズできるように(端数が出ても合わせやすい)
(set-frame-parameter f 'frame-resize-pixelwise t)
(let* ((attrs (frame-monitor-attributes f))
(geom (alist-get 'geometry attrs)) ; (X Y WIDTH HEIGHT) in pixels
(mx (nth 0 geom))
(my (nth 1 geom))
(mw (nth 2 geom))
(mh (nth 3 geom))
(half (/ mw 2)))
;; 左上へ移動
(set-frame-position f mx my)
;; 幅=半分、高さ=全部(第4引数 t で pixel 指定)
(set-frame-size f half mh t)))))
;; 通常起動(最初のフレーム)
(add-hook 'window-setup-hook #'my/frame-left-half)
;; emacs --daemon で起動して、後から作られるフレームにも適用
(add-hook 'after-make-frame-functions #'my/frame-left-half)Code language: JavaScript (javascript)
フレーム作成後に確実に効かせるため window-setup-hook1 と、daemon起動にも対応するため after-make-frame-functions2 を使います。
frame-resize-pixelwise を有効にすると、文字セル境界に縛られず、左半分ぴったりにしやすくなります3。
2. frame-monitor-attributesのgeometry
現在のフレームが表示されているモニタの位置と大きさを取得するには、frame-monitor-attributes を使います4。
この関数はフレームが属しているモニタの属性をalistで返し、geometry という項目に位置とサイズが格納されています。
(let* ((attrs (frame-monitor-attributes f))Code language: JavaScript (javascript)
alist-get は alist からキーに対応する値を取り出す関数で、geometry は (X Y WIDTH HEIGHT) という4要素のリストです5。
(geom (alist-get 'geometry attrs))Code language: JavaScript (javascript)
nth でリストのn番目の要素を取得し、モニタの座標とサイズを変数に格納します6。
(mx (nth 0 geom))
(my (nth 1 geom))
(mw (nth 2 geom))
(mh (nth 3 geom))
モニタ幅 mw を2で割り、左半分の幅を計算します7。
(half (/ mw 2)))
2.1. geometryだとドックサイズが考慮されない
ただし、geometryはモニタの物理的なピクセル領域全体を返します8。
座標の原点は画面の物理的な左上で、Dockやメニューバーがどこにあっても関係なく、常に(0 0 2560 1440)のような値になります。
Dockが左側にある場合、mxは0を指したままです。そこにフレームを配置すると、Dockと重なってしまいます。
2.2. workareaに変更した
そこで、画面表示領域を取得するキーをgeometryからworkareaに変えました。
(geom (alist-get 'geometry attrs))
↓
(wa (or (alist-get 'workarea attrs)
(alist-get 'geometry attrs)))Code language: JavaScript (javascript)
workareaはdisplay-monitor-attributes-listが返すモニタ属性のひとつで、DockとメニューバーをOSが除外した後の実際に使える領域を(left top width height)の形式で持っています9。
(defun my/frame-left-half (&optional frame)
"FRAME を現在のディスプレイの workarea 左半分に配置する。"
(let ((f (or frame (selected-frame))))
(when (display-graphic-p f)
(set-frame-parameter f 'frame-resize-pixelwise t)
(let* ((attrs (frame-monitor-attributes f))
(wa (or (alist-get 'workarea attrs)
(alist-get 'geometry attrs)))
(mx (nth 0 wa))
(my (nth 1 wa))
(mw (nth 2 wa))
(mh (nth 3 wa))
(half (/ mw 2)))
(set-frame-position f mx my)
(set-frame-size f half mh t)))))Code language: JavaScript (javascript)
Dockが左にある場合、(nth 0 wa)、つまりmxはDock幅分だけ右にずれた値になります。set-frame-positionにそのまま渡すと、フレームがDockの右端から始まります。
高さ方向も同様で、myにはメニューバーの高さが反映されています。
orでフォールバックを入れているのは、workareaを返さない環境(一部のLinuxディスプレイサーバーなど)でも動作させるためです10。
3. 補足:frame-resize-pixelwise
コード中の(set-frame-parameter f 'frame-resize-pixelwise t)は、フレームのリサイズをピクセル単位で行うための設定です。
デフォルトはnilで、その場合フレームサイズはフォントの文字幅・文字高の倍数に丸められます11。workareaの幅を2で割った値がフォント幅で割り切れない場合、tにしておかないと右端に数ピクセルの隙間が生じます。
(set-frame-size f half mh t)の末尾のtは、halfとmhをピクセル数として解釈させる引数です12。
省略すると文字数として解釈されるため、workareaから取得したピクセル値をそのまま渡しても意図した幅にはなりません。
- window-setup-hookは、Emacsの初期化処理の最後、フレームが表示される直前に実行される標準フックです。通常起動時に1回だけ呼ばれます – Standard Hooks (GNU Emacs Lisp Reference Manual)
- after-make-frame-functionsは、新しいフレームが作成されるたびに実行される異常フック(abnormal hook)です。各関数は新しく作成されたフレームを引数として受け取ります – Creating Frames (GNU Emacs Lisp Reference Manual)
- frame-resize-pixelwiseをnilにすると、フレームサイズは文字の行列単位で丸められます。non-nilに設定すると、ピクセル単位でのリサイズが可能になり、一部のウィンドウマネージャでフレームを真に最大化またはフルスクリーン表示するために必要です – Frame Size (GNU Emacs Lisp Reference Manual)
- frame-monitor-attributesは、フレームが属している物理モニタの属性を返す関数です。戻り値はalistで、geometry(位置とサイズ)、workarea(作業領域)、mm-size(物理サイズ)などの情報が含まれます – Multiple Terminals (GNU Emacs Lisp Reference Manual)
- alist-getはEmacs 25.1以降で利用可能な関数で、連想リスト(alist)から指定されたキーに対応する値を取得します。
第3引数でデフォルト値を指定でき、第4引数でキーの比較方法を変更できます - nth関数は0から始まるインデックスでリストの要素を取得します。第1引数が0なら最初の要素、1なら2番目の要素を返します。リストがn番目の要素を持たない場合はnilを返します
- set-frame-positionは、フレームの外枠の左上隅の位置をピクセル単位で設定します。正の値は表示原点からのオフセット、負の値は右端または下端からのオフセットとして解釈されます – Frame Position (GNU Emacs Lisp Reference Manual)
geometryの値は(X Y WIDTH HEIGHT)の形式で、モニタ全体のサイズを表します。frame-monitor-attributesはEmacs 25以降で使えるようになった関数で、フレームが属しているモニタだけの属性を返すため、display-monitor-attributes-listで全モニタを取得してから絞り込む手間が不要です。 – Multiple Terminals (GNU Emacs Lisp Reference Manual)- Emacsのリファレンスマニュアルではworkareaを「ウィンドウマネージャの機能(ドック、タスクバー等)が占有するスペースを除いた、使用可能な表示領域」と定義しています。プラットフォームによって詳細は異なります。 – Multiple Terminals (GNU Emacs Lisp Reference Manual)
- LinuxではGTKなしでビルドされたEmacsや、XRandR/Xinerama拡張が無効な環境では、複数モニタの情報が1つのモニタとして扱われ、
workareaがgeometryと同一の値になることがあります。source属性が"fallback"の場合がこれにあたります。 – Multiple Terminals (GNU Emacs Lisp Reference Manual) frame-resize-pixelwiseがnilのとき、set-frame-sizeにピクセル値を渡しても、ウィンドウマネージャーやツールキット(GTK+など)が文字単位に丸めてしまい、指定通りのサイズにならないことがあります。この変数はEmacs 24.4で導入されました。 – Frame Size (GNU Emacs Lisp Reference Manual)set-frame-sizeの第4引数pixelwiseがnil(省略時)のとき、第2・第3引数は文字数として解釈されます。tを渡すとピクセル数として扱われます。frame-resize-pixelwiseがnilの環境では、この引数をtにしても期待通りに動作しないことがあります。 – Frame Size (GNU Emacs Lisp Reference Manual)