Subject: Re: bug#38705: 26.3; set-frame-size pixelwise makes frame larger

From: martin rudalics <[email-masked]>
Date: Wed, 25 Dec 2019 10:49:34 +0100

> I tried to set up the frame outer size like below.
 >    ;;; sample ~/.emacs.d/init.el
 >    (defun test-set-frame-outer-size (frame width height)
 >      (set-frame-size frame width height t)
 >      (set-frame-size frame
 >                      (- width  (- (frame-outer-width  frame) width))
 >                      (- height (- (frame-outer-height frame) height))
 >                      t))
 >    (setq frame-inhibit-implied-resize t)
 >    (test-set-frame-outer-size nil 500 500)
 >    ;;; sample ~/.emacs.d/init.el end
 >
 > But the function does not work well inside init.el.
 > After emacs started, the size is:
 >    (list (frame-outer-width) (frame-outer-height))
 >    =>(500 502)
 > When user input is allowed, evaluating
 >    (test-set-frame-outer-size nil 500 500)
 > works as expected.

Look up the function 'frame-geometry'.  If its return value for
something like the 'external-border-size' differs for a frame before
and after "user input is allowed" then it's because that something has
not been specified before the frame was "mapped" and so Emacs won't
know it.

If you are sure that your decorations won't change from one emacs
session to the next, you can try to remember them from the last
session and apply them manually.  However, in general, calling
'frame-outer-width/-height' from init.el is not a good idea.

 > About another point.
 > If the frame has no menu-bar, tool-bar, scroll-bar nor fringes,
 > frame-pixel-width and frame-pixel-height can be set by set-frame-size.
 > But if the frame is undecorated, the outer size is as large as the size
 > of non-undecorated frame regardless of the value of
 > frame-inhibit-implied-resize.
 > The inner size is enlarged to the outer size after redisplay.
 > The size can not be set as expected.
 >    (let ((width 500) (height 500)
 >          (params '((undecorated . t)
 >                    (menu-bar-lines . 0)
 >                    (tool-bar-lines . 0)
 >                    (horizontal-scroll-bars . nil)
 >                    (vertical-scroll-bars . nil)
 >                    (left-fringe . 0)
 >                    (right-fringe . 0)))
 >          (f (lambda () (list (list (frame-outer-width)
 >                                    (frame-inner-width))
 >                              (list (frame-outer-height)
 >                                    (frame-inner-height))))))
 >      (setq frame-inhibit-implied-resize t)
 >      (modify-frame-parameters nil params)
 >      (set-frame-size nil width height t)
 >      (list (funcall f)
 >            (progn (redisplay) (funcall f))))
 >    =>(((516 500) (539 500)) ((516 516) (539 539)))
 >
 > If the form above is evaluated twice, the frame outer size stays
 > 516x539 pixels and the inner size shrinks to 500x500 pixels.

Please note again that the values you pass to 'set-frame-size' are the
values later reported by 'frame-text-width' and 'frame-text-height'.
These are fictive values and neither inner, nor outer or native sizes
might match these.

 > The inner text area is displayed at the top-left,
 > and there are blank areas at the right and the bottom.

This is the result of a bug that made frame resizing incorrect for
undecorated frames on Windows ever since.  To explain why this bug
usually goes unnoticed note that 'set-frame-size' causes three things
to happen: (1) it asks the window manager to resize the frame's
(window system) window.  (2) it "prematurely" resizes the frame's
windows because in their code some users want to know the new window
sizes right away.

Now if (1) eventually results in a frame size different from the one
expected in (2), the corresponding message from the window manager
will cause Emacs to (3) re-resize the frame's windows if they don't
match and everything should go well.  Usually, the sizes returned by
the window manager match those expected by (2) and in (3) no such
re-resizing is needed.

But in your scenario the first

(set-frame-size nil 500 500 t)

call will, due to the bug, pass incorrect values to the window
manager, which makes the values eventually returned by the window
manager differ from those calculated in (2).  (3) will fix this.  But
the subsequent calls will cause the window manager do nothing in (1)
because it is supplied the old values again and thus will decide that
no resizing is needed.  Yet Emacs will perform (2) with the values it
expects and these values will never be corrected because (3) will
never happen.

Please try the attached patch.  I will install it on Emacs-27 as soon
as I have upgraded my working environment, something which may happen
in a couple of days only.

Many thanks again for reporting these obscure behaviors, martin
diff --git a/src/w32fns.c b/src/w32fns.c
index bf2a7a3e54..8145b82416 100644
--- a/src/w32fns.c
+++ b/src/w32fns.c
@@ -2146,6 +2146,9 @@ w32_set_undecorated (struct frame *f, Lisp_Object new_value, Lisp_Object old_val
 		    | SWP_FRAMECHANGED);
       FRAME_UNDECORATED (f) = false;
     }
+
+  f->output_data.w32->dwStyle = GetWindowLong (hwnd, GWL_STYLE);
+
   unblock_input ();
 }