Automatically Center New Emacs Windows (Aka Frames) on Screen
When I open a new GUI window of Emacs on macOS (which Emacs calls frame) it’s positioned in the top-left corner. Since I have an ultrawide monitor at my desk, that’s pretty annoying. Unlike regular macOS apps, Emacs doesn’t remember where I dragged the last NSWindow
to, so it doesn’t spawn new windows next to that. It also doesn’t stagger them like NSDocument
-type apps usually do.
So I took some code from the internet to determine the window’s center position and the screen’s center position and then align both. It’s part of a larger Gist that would even tile your windows on the screen, but I only want new windows to always start in the middle. I can move them ouyt of the way if needed.
So the whole code is from https://gist.github.com/ieure/80638, only shortened for this particular use case.
Update 2021-06-09: The code below only works on single-monitor setups. If you have 2+ monitors, this unintentionally tries to center the window in the area described by all monitors combined. I published a fixed version that I encourage you to use instead!
Update 2022-04-22: There’s a much shorter version that is likely even more robust shared by Louis Brauer. See the new solution.
(defun ct/screen-usable-height (&optional display)
"Return the usable height of the display.
Some window-systems have portions of the screen which Emacs
cannot address. This function should return the height of the
screen, minus anything which is not usable."
(- (display-pixel-height display)
(cond ((eq window-system 'ns) 22) ;; macOS Menu Bar offset
(t 0))))
(defun ct/screen-usable-width (&optional display)
"Return the usable width of the display."
(display-pixel-width display))
(defun ct/center-box (w h cw ch)
"Center a box inside another box.
Returns a list of `(TOP LEFT)' representing the centered position
of the box `(w h)' inside the box `(cw ch)'."
(list (/ (- cw w) 2) (/ (- ch h) 2)))
(defun ct/frame-get-center (frame)
"Return the center position of FRAME on it's display."
(let ((disp (frame-parameter frame 'display)))
(ct/center-box (frame-pixel-width frame) (frame-pixel-height frame)
(ct/screen-usable-width disp) (ct/screen-usable-height disp))))
(defun ct/frame-center (&optional frame)
"Center a frame on the screen."
(interactive)
(apply 'set-frame-position
(let* ((frame (or (and (boundp 'frame) frame) (selected-frame)))
(center (ct/frame-get-center frame)))
;; Flatten the X/Y list in `center` into a single list with `frame`
;; so this list can be applied as parameters to `set-frame-position`:
`(,frame ,@center))))
Update 2021-11-06: I noticed that some time after I began using Emacs 28, (and (boundp 'frame) frame)
caused trouble – a working solution to auto-center a frame after making it is to omit the boundp
check and just write this: (let* ((frame (or frame (selected-frame))) ...
Then to automatically center the frame after frame creation:
(add-to-list 'after-make-frame-functions #'ct/frame-center)
All these Emacs Lisp backtick-at-comma shorthands begin to confuse me. I’m fine with #'functionname
to signity that this isn’t just a string, but a function name. But the commas, and the backticked list, and the at sign?! Geez.
If we skipped the shorthand characters and just wrote (list frame center)
, we’d get the elements (frame (top left))
, because center
itself is a tuple already. This essentially flattens the nested list.
That’s the thing with these quotes. They are not just shorthands. The result of (list frame center)
is different from '(frame center)
. Both expressions produces a list, but if you call the list
function, the variadic arguments are evaluated and their result is put into the list; if you use the single quote, you get a list of the literal elements. It’s equivalent to (list 'frame 'center)
, which we clearly didn’t want. The backquote allows to evaluate some elements of the quotes list with the ,
operator, and by evaluating the variables, we get their names in the original code.
Or we don’t use the quoted forms and have a straight-forward result.
Compare these:
- Quoted:
\
(,frame ,@center)` packs a lot of info in characters that are harder to look up than functions; - Function calls
(flatten-list (list frame center))
is very much readable.
Here’s how I’d really write it, putting the local variable bindings first and not inside the function call to put less lines between function name and parameter list:
(defun ct/frame-center (&optional frame)
"Center a frame on the screen."
(interactive)
(let* ((frame (or (and (boundp 'frame) frame) (selected-frame)))
(center (ct/frame-get-center frame)))
(apply 'set-frame-position (flatten-list (list frame center)))))