My laptop has a 4K screen therefore a very high DPI, much higher than my external monitors. So when I switch from using external monitors to the builtin laptop screen I would like to quickly scale the text and, if possible, all the GUI widgets in Emacs to suite the new DPI. I'm aware of set-frame-font but I haven't found a corresponding function that adjusts the scaling aswell. And text-scale-adjust only works on a buffer locally.

3 Answers 3

  1. You can set the value of default-frame-alist, including its font parameter, conditionally, depending on the current monitor/display. (You can use function display-monitor-attributes-list to give you the monitor/display information.) This lets you use different default font sizes for different monitors or other different contexts.

  2. You can also change font size dynamically, for all windows and buffers shown in any frame, using command zoom-in, zoom-out, or zoom-in/out, from library zoom-frm.el.

    These same commands can either zoom a buffer (in all windows, on whatever frame) or a frame (all of its windows, whatever their buffer). See Emacs Wiki page Set Fonts.


I'm using something similar to @gavenkoa (by pure coincidence also with a helper named my-dpi!) to make new frames get the right size, but calling on zoom-frm since I use that package anyway. It also works on multiheaded setups, by using frame-monitor-attributes instead of display-monitor-attributes-list:

(defun my-dpi (&optional frame)
  "Get the DPI of FRAME (or current if nil)."
  (cl-flet ((pyth (lambda (w h)
                    (sqrt (+ (* w w)
                             (* h h)))))
            (mm2in (lambda (mm)
                     (/ mm 25.4))))
    (let* ((atts (frame-monitor-attributes frame))
           (pix-w (cl-fourth (assoc 'geometry atts)))
           (pix-h (cl-fifth (assoc 'geometry atts)))
           (pix-d (pyth pix-w pix-h))
           (mm-w (cl-second (assoc 'mm-size atts)))
           (mm-h (cl-third (assoc 'mm-size atts)))
           (mm-d (pyth mm-w mm-h)))
      (/ pix-d (mm2in mm-d)))))

(defvar my-zoom-frm-wanted-dpi 70
  "The DPI I want to achieve when using `my-zoom-frm-by-dpi'.")

(defun my-zoom-frm-by-dpi (&optional frame)
  "Zoom FRAME so the DPI is closer to `my-zoom-frm-wanted-dpi'."
  (let ((frame (or frame (selected-frame))))
    (when (frame-parameter frame 'zoomed)
      (zoom-frm-unzoom frame))
    (let ((frame-zoom-font-difference (1- (round (/ (my-dpi frame)
      (when (called-interactively-p 'interactive)
        (message "Zooming by %S" frame-zoom-font-difference))
      (zoom-frm-in frame))))

;; Apply the scaling I want to each newly created frame:
(add-hook 'after-make-frame-functions #'my-zoom-frm-by-dpi)
  • If there is a way to trigger this automatically when frame is first displayed at different display and DPI has changed?
    – krokodil
    Commented Nov 12, 2019 at 20:44
  • (add-hook 'after-make-frame-functions #'my-zoom-frm-by-dpi) will make it run on creating the frame. I don't know if there's a hook for moving an existing frame across displays, if that's what you meant.
    – unhammer
    Commented Nov 13, 2019 at 8:57
  • 3
    Found this: github.com/mnp/dispwatch
    – krokodil
    Commented Nov 15, 2019 at 22:59
  • 2
    I found that on Windows, the my-dpi function is a little more accurate if you use the 'workarea' attribute instead of the 'geometry' attribute. This is because the 'mm-size' attribute is the size of the area not obscured by taskbars and so on.
    – glmorous
    Commented Nov 20, 2019 at 17:59
  • After the edit, display isn't used in my-dpi right?
    – KevinOrr
    Commented Mar 12, 2020 at 14:36

I made my config portable across different laptops/desktops with:

(defun my-dpi ()
  (let* ((attrs (car (display-monitor-attributes-list)))
         (size (assoc 'mm-size attrs))
         (sizex (cadr size))
         (res (cdr (assoc 'geometry attrs)))
         (resx (- (caddr res) (car res)))
    (catch 'exit
      ;; in terminal
      (unless sizex
        (throw 'exit 10))
      ;; on big screen
      (when (> sizex 1000)
        (throw 'exit 10))
      ;; DPI
      (* (/ (float resx) sizex) 25.4))))

(defun my-preferred-font-size ()
  (let ( (dpi (my-dpi)) )
    ((< dpi 110) 10)
    ((< dpi 130) 11)
    ((< dpi 160) 12)
    (t 12))))

(defvar my-preferred-font-size (my-preferred-font-size))

(defvar my-regular-font
   ((eq window-system 'x) (format "DejaVu Sans Mono-%d:weight=normal" my-preferred-font-size))
   ((eq window-system 'w32) (format "Courier New-%d:antialias=none" my-preferred-font-size))))
(defvar my-symbol-font
   ((eq window-system 'x) (format "DejaVu Sans Mono-%d:weight=normal" my-preferred-font-size))
   ((eq window-system 'w32) (format "DejaVu Sans Mono-%d:antialias=none" my-preferred-font-size))))

 ((eq window-system 'x)
  (if (and (fboundp 'find-font) (find-font (font-spec :name my-regular-font)))
      (set-frame-font my-regular-font)
    (set-frame-font "7x14")))
 ((eq window-system 'w32)
  (set-frame-font my-regular-font)
  (set-fontset-font nil 'cyrillic my-regular-font)
  (set-fontset-font nil 'greek my-regular-font)
  (set-fontset-font nil 'phonetic my-regular-font)
  (set-fontset-font nil 'symbol my-symbol-font)))

Though I haven't tried to make it works in multiheaded setup. Don't know if it's ever possible.

  • 1
    This actually works better for me when fixing the scaling factor in my-dpi from 25.4 to 17.2 (since emacs using xrandr for monitor dimensions, not xdpyinfo)
    – Greg B
    Commented May 5, 2020 at 12:22
  • Can you please describe in more details what your code does. Or minimally use in-code comments to describe it. It is hard to understand for newbies.
    – buhtz
    Commented Oct 29, 2021 at 7:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.