9

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.

7
  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.

3

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 display)
  "Get the DPI of DISPLAY.
DISPLAY is a display name, frame or terminal, as in
`display-monitor-attributes-list'."
  (cl-flet ((pyth (lambda (w h)
                    (sqrt (+ (* w w)
                             (* h h)))))
            (mm2in (lambda (mm)
                     (/ mm 25.4))))
    (let* ((atts (frame-monitor-attributes))
           (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'."
  (interactive)
  (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)
                                                    my-zoom-frm-wanted-dpi)))))
      (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 Nov 12 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 Nov 13 at 8:57
  • 2
    Found this: github.com/mnp/dispwatch – krokodil Nov 15 at 22:59
  • 1
    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 Nov 20 at 17:59
1

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)))
         dpi)
    (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)) )
  (cond
    ((< dpi 110) 10)
    ((< dpi 130) 11)
    ((< dpi 160) 12)
    (t 12))))

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

(defvar my-regular-font
  (cond
   ((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
  (cond
   ((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))))

(cond
 ((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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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